《学会 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 接口,你可以灵活地处理控制器方法的参数解析,满足复杂业务需求。在实际开发中,合理使用自定义参数解析器可以提高代码的可读性和可维护性。

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

相关推荐
倔强的石头_5 分钟前
【金仓数据库】ksql 指南(六)—— 创建与管理用户和权限(KingbaseES 安全控制核心)
数据库
奋进的芋圆1 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin1 小时前
设计模式之桥接模式
java·设计模式·桥接模式
小熊officer1 小时前
Python字符串
开发语言·数据库·python
model20051 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
武藤一雄1 小时前
C# 关于多线程如何实现需要注意的问题(持续更新)
windows·后端·microsoft·c#·.net·.netcore·死锁
渐暖°1 小时前
JDBC直连ORACLE进行查询
数据库·oracle
荒诞硬汉1 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国2 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
萧曵 丶2 小时前
Next-Key Lock、记录锁、间隙锁浅谈
数据库·sql·mysql·mvcc·可重复读·幻读