写在前面的话
前几篇博文,大致了解了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 请求实体。
下面是若干示例:
@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 接口。
@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的配置类中,添加该入参解析器:
@Slf4j
public class CustomConfig implements WebMvcConfigurer {
/**
* 添加入参处理器
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new MyHandlerMethodArgumentResolver());
}
}
Step3、编写测试类
/**
* 测试自定义入参解析器
*/
@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 接口,你可以灵活地处理控制器方法的参数解析,满足复杂业务需求。在实际开发中,合理使用自定义参数解析器可以提高代码的可读性和可维护性。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。