《学会 SpringMVC 系列 · 参数解析器 ArgumentResolvers》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗

🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

写在前面的话

前几篇博文,大致了解了SpringMVC请求流程中的参数与返回值的源码分析,后续的几篇博文,会将流程中涉及的若干关键环节单独拿出来讲解,并结合实战中的运用,帮助领略SpringMVC带来的定制和扩展能力。

本篇文章先介绍一下 ArgumentResolvers 相关内容。

相关博文
《学会 SpringMVC 系列 · 基础篇》
《学会 SpringMVC 系列 · 剖析篇(上)》
《学会 SpringMVC 系列 · 剖析入参处理》
《学会 SpringMVC 系列 · 剖析出参处理》
《学会 SpringMVC 系列 · 返回值处理器》
《学会 SpringMVC 系列 · 消息转换器 MessageConverters》
《学会 SpringMVC 系列 · 写入拦截器 ResponseBodyAdvice》
《学会 SpringMVC 系列 · 剖析初始化》
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》


ArgumentResolvers

技术简介

Spring MVC 的 ArgumentResolvers 参数解析器是负责将请求参数解析为控制器方法参数的关键组件,负责处理请求参数的解析和转换。当一个请求到达控制器时,Spring MVC 使用一系列的ArgumentResolvers来解析传入的参数,根据请求参数的名称和类型,将它们转换为控制器方法可以接受的对象。

Spring MVC 同时提供了 HandlerMethodArgumentResolver 接口,它定义了如何解析方法参数。Spring MVC在处理请求时,会遍历所有的HandlerMethodArgumentResolver实现,尝试解析方法参数。通过该接口可以自定义参数解析器。通过实现这个接口,你可以为控制器方法的参数提供自定义的解析逻辑。这在处理复杂对象、请求体、请求参数等场景中非常有用。


内置参数解析器

Spring MVC 默认提供了多种参数解析器,例如:

  • RequestResponseBodyMethodProcessor: 处理 @RequestBody 和 @ResponseBody 注解,用于解析请求体的 JSON 数据。
  • PathVariableMethodArgumentResolver: 处理 @PathVariable 注解,用于解析 URL 路径中的变量。
  • RequestParamMethodArgumentResolver: 处理 @RequestParam 注解,用于解析请求参数。
  • RequestHeaderMethodArgumentResolver: 处理 @RequestHeader 注解,用于解请求头信息。
  • HttpEntityMethodProcessor: 处理 HttpEntity 类型参数,用于接收 HTTP 请求实体。

下面是若干示例:

java 复制代码
@GetMapping("/example")
public String example(@RequestParam("name") String name) {
    return "Hello, " + name;
}

@GetMapping("/users/{id}")
public String getUserById(@PathVariable("id") Long userId) {
    return "User ID is: " + userId;
}

@PostMapping("/users")
public String createUser(@RequestBody User user) {
    // 保存用户信息
    return "User created successfully";
}

@GetMapping("/example")
public String example(@RequestHeader("X-Custom-Header") String headerValue) {
    return "Header value: " + headerValue;
}

自定义参数解析器

当内置的参数解析器无法满足你的需求时,例如解析复杂的自定义对象或者从非标准的地方获取数据。

你可以通过实现HandlerMethodArgumentResolver接口来创建自定义参数解析器。

该接口定义了两个方法:

supportsParameter:判断当前解析器是否支持解析指定参数

resolveArgument:解析参数并返回解析结果

【具体步骤】

Step1、自定义一个 MyHandlerMethodArgumentResolver,实现 HandlerMethodArgumentResolver 接口。

java 复制代码
@Slf4j
public class MyHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

    /**
     * 用于判定是否需要处理该参数分解,返回true为需要,并会去调用下面的方法resolveArgument
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return Student.class.isAssignableFrom(parameter.getParameterType());
    }

    /**
     * 真正用于处理参数分解的方法,返回的Object就是controller方法上的形参对象
     * 用途:仅仅用于测试,解析请求体内容,比如name#张三,age#20,将内容解析组装成Student对象再返回
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String str = getRequestBody(webRequest);
        String[] split = str.split(",");
        String name = split[0].split("#")[1];
        String age = split[1].split("#")[1];
        return Student.builder()
                .name(name)
                .age(Integer.parseInt(age))
                .id(1)
                .build();
    }

    /**
     * 从请求体获取内容
     * 也可以参考RequestResponseBodyMethodProcessor的读取方式
     */
    private String getRequestBody(NativeWebRequest webRequest) throws IOException {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        assert request != null;
        BufferedReader reader = request.getReader();
        StringBuilder sb = new StringBuilder();
        char[] buf = new char[1024];
        int rd;
        while ((rd = reader.read(buf)) != -1) {
            sb.append(buf, 0, rd);
        }
        return sb.toString();
    }
}

Step2、再SpringMVC的配置类中,添加该入参解析器:

java 复制代码
@Slf4j
public class CustomConfig implements WebMvcConfigurer {

    /**
     * 添加入参处理器
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new MyHandlerMethodArgumentResolver());
    }
}

Step3、编写测试类

java 复制代码
/**
 * 测试自定义入参解析器
 */
@ResponseBody
@RequestMapping("/studyJsonCustom")
public Student studyJsonCustom(Student student) {
    student.setEmail("战神");
    return student;
}

Step4、启动项目,验证一下效果


实战场景

上述示例只是为了帮助理解,真实开发中,更多自定义入参解析器的情况是:添加自定义注解,并为其指定特定功能,例如添加可以同时兼容form 和 json 的场景、或支持复杂数据自动解析等等。

自定义参数解析器的应用场景包含但不限于:

  • 复杂对象的解析:当请求参数较多且需要封装成对象时,使用自定义解析器可以简化控制器代码。
  • 请求头解析:可以根据请求头中的信息创建对象。
  • 安全性:可以在解析参数时进行一些安全检查,比如验证用户身份。
  • 数据转换:可以在解析参数时进行数据格式转换,比如将字符串转换为日期对象。

注意事项:

  • 确保supportsParameter方法能够准确地识别需要解析的参数。
  • 在resolveArgument方法中实现具体的解析逻辑,注意异常处理。
  • 考虑解析器的执行顺序,因为Spring MVC会按照注册顺序尝试解析参数。

值得一提的是,解析器可以注册这个,但最终只会生效一个,如果都找不到符合的,都会有兜底的方案,要适当注意一下添加的顺序。


源码知识回顾

本篇为 SpringMVC 源码分析系列文章,总结回顾一下全流程。

【一次请求的主链路节点】

DispatcherServlet#doDispatch(入口方法)

DispatcherServlet#getHandler(根据path找到对应的HandlerExecutionChain

DispatcherServlet#getHandlerAdapter(根据handle找到对应的HandlerAdapter

HandlerExecutionChain#applyPreHandle(触发拦截器的前置逻辑)

AbstractHandlerMethodAdapter#handle(核心逻辑)

HandlerExecutionChain#applyPostHandle(触发拦截器的后置逻辑)

【核心handle方法的主链路节点】

RequestMappingHandlerAdapter#handleInternal(入口方法)

RequestMappingHandlerAdapter#invokeHandlerMethod(入口方法2)

ServletInvocableHandlerMethod#invokeAndHandle(入口方法3)

InvocableHandlerMethod#invokeForRequest(参数和实际执行的所在,3.1)

InvocableHandlerMethod#getMethodArgumentValues(参数处理,3.1.1)

InvocableHandlerMethod#doInvoke(实际执行,3.1.2)

HandlerMethodReturnValueHandlerComposite#handleReturnValue(返回处理,3.2)

【针对 @RequestBody 和 @ResponseBody 场景】


总结陈词

本篇博文继请求链路源码分析后,继续介绍了参数解析器ArgumentResolvers的用法,除了熟悉内置参数解析器的原理外,通过实现 HandlerMethodArgumentResolver 接口,你可以灵活地处理控制器方法的参数解析,满足复杂业务需求。在实际开发中,合理使用自定义参数解析器可以提高代码的可读性和可维护性。

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

相关推荐
也无晴也无风雨28 分钟前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
憨子周30 分钟前
2M的带宽怎么怎么设置tcp滑动窗口以及连接池
java·网络·网络协议·tcp/ip
FIN技术铺2 小时前
Redis集群模式之Redis Sentinel vs. Redis Cluster
数据库·redis·sentinel
霖雨2 小时前
使用Visual Studio Code 快速新建Net项目
java·ide·windows·vscode·编辑器
SRY122404192 小时前
javaSE面试题
java·开发语言·面试
Fiercezm2 小时前
JUC学习
java
无尽的大道3 小时前
Java 泛型详解:参数化类型的强大之处
java·开发语言
ZIM学编程3 小时前
Java基础Day-Sixteen
java·开发语言·windows
我不是星海3 小时前
1.集合体系补充(1)
java·数据结构
P.H. Infinity3 小时前
【RabbitMQ】07-业务幂等处理
java·rabbitmq·java-rabbitmq