共计 12900 个字符,预计需要花费 33 分钟才能阅读完成。
作者:京东批发 王鹏超
1. 什么是参数解析器
@RequstBody、@RequstParam 这些注解是不是很相熟?
咱们在开发 Controller 接口时常常会用到此类参数注解,那这些注解的作用是什么?咱们真的理解吗?
简略来说,这些注解就是帮咱们将前端传递的参数间接解析成间接能够在代码逻辑中应用的 javaBean,例如 @RequstBody 接管 json 参数,转换成 java 对象,如下所示:
前台传参 | 参数格局 |
---|---|
{“userId”: 1, “userName”: “Alex”} | application/json |
失常代码书写如下:
@RequestMapping(value = "/getUserInfo")
public String getUserInfo(@RequestBody UserInfo userInfo){
//***
return userInfo.getName();}
但如果是服务接管参数的形式扭转了,如下代码,参数就不能胜利接管了,这个是为什么呢?
@RequestMapping(value = "/getUserInfo")
public String getUserInfo(@RequestBody String userName, @RequestBody Integer userId){
//***
return userName;
}
如果下面的代码略微改变一下注解的应用并且前台更改一下传参格局,就能够失常解析了。
前台传参 | 参数格局 |
---|---|
http://*?userName=Alex&userId=1 | 无 |
@RequestMapping(value = "/getUserInfo")
public String getUserInfo(@RequestParam String userName, @RequestParam Integer userId){
//***
return userName;
}
这些这里就不得不引出这些注解背地都对应的内容—Spring 提供的参数解析器,这些参数解析器帮忙咱们解析前台传递过去的参数,绑定到咱们定义的 Controller 入参上,不通类型格局的传递参数,须要不同的参数解析器,有时候一些非凡的参数格局,甚至须要咱们自定义一个参数解析器。
不论是在 SpringBoot 还是在 Spring MVC 中,一个 HTTP 申请会被 DispatcherServlet 类接管(实质是一个 Servlet,继承自 HttpServlet)。Spring 负责从 HttpServlet 中获取并解析申请,将申请 uri 匹配到 Controller 类办法,并解析参数并执行办法,最初解决返回值并渲染视图。
参数解析器的作用就是将 http 申请提交的参数转化为咱们 controller 处理单元的入参。原始的 Servlet 获取参数的形式如下,须要手动从 HttpServletRequest 中获取所需信息。
@WebServlet(urlPatterns="/getResource")
public class resourceServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
/** 获取参数开始 */
String resourceId = req.getParameter("resourceId");
String resourceType = req.getHeader("resourceType");
/** 获取参数完结 */
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.println("resourceId" + resourceId + "resourceType" + resourceType);
}
}
Spring 为了帮忙开发者解放生产力,提供了一些特定格局(header 中 content-type 对应的类型)入参的参数解析器,咱们在接口参数上只有加上特定的注解(当然不加注解也有默认解析器),就能够间接获取到想要的参数,不须要咱们本人去 HttpServletRequest 中手动获取原始入参,如下所示:
@RestController
public class resourceController {@RequestMapping("/resource")
public String getResource(@RequestParam("resourceId") String resourceId,
@RequestParam("resourceType") String resourceType,
@RequestHeader("token") String token) {return "resourceId" + resourceId + "token" + token;}
}
罕用的注解类参数解析器应用形式以及与注解的对应关系对应关系如下:
注解命名 | 搁置地位 | 用处 |
---|---|---|
@PathVariable | 搁置在参数前 | 容许 request 的参数在 url 门路中 |
@RequestParam | 搁置在参数前 | 容许 request 的参数间接连贯在 url 地址前面, 也是 Spring 默认的参数解析器 |
@RequestHeader | 搁置在参数前 | 从申请 header 中获取参数 |
@RequestBody | 搁置在参数前 | 容许 request 的参数在参数体中, 而不是间接连贯在地址前面 |
注解命名 | 对应的解析器 | content-type |
---|---|---|
@PathVariable | PathVariableMethodArgumentResolver | 无 |
@RequestParam | RequestParamMethodArgumentResolver | 无(get 申请)和 multipart/form-data |
@RequestBody | RequestResponseBodyMethodProcessor | application/json |
@RequestPart | RequestPartMethodArgumentResolver | multipart/form-data |
2. 参数解析器原理
要理解参数解析器,首先要理解一下最原始的 Spring MVC 的执行过程。客户端用户发动一个 Http 申请后,申请会被提交到前端控制器(Dispatcher Servlet),由前端控制器申请处理器映射器(步骤 1),处理器映射器会返回一个执行链(Handler Execution 步骤 2),咱们通常定义的拦截器就是在这个阶段执行的,之后前端控制器会将映射器返回的执行链中的 Handler 信息发送给适配器(Handler Adapter 步骤 3), 适配器会依据 Handler 找到并执行相应的 Handler 逻辑,也就是咱们所定义的 Controller 管制单元(步骤 4),Handler 执行结束会返回一个 ModelAndView 对象,后续再通过视图解析器解析和视图渲染就能够返回给客户端申请响应信息了。
在容器初始化的时候,RequestMappingHandlerMapping 映射器会将 @RequestMapping 注解正文的办法存储到缓存,其中 key 是 RequestMappingInfo,value 是 HandlerMethod。HandlerMethod 是如何进行办法的参数解析和绑定,就要理解申请参数适配器 **RequestMappingHandlerAdapter,** 该适配器对应接下来的参数解析及绑定过程。源码门路如下:
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
RequestMappingHandlerAdapter大抵的解析和绑定流程如下图所示,
RequestMappingHandlerAdapter 实现了接口 InitializingBean,在 Spring 容器初始化 Bean 后,调用办法 afterPropertiesSet(),将默认参数解析器绑定 HandlerMethodArgumentResolverComposite 适配器的参数 argumentResolvers 上,其中 HandlerMethodArgumentResolverComposite 是接口 HandlerMethodArgumentResolver 的实现类。源码门路如下:
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
if (this.argumentResolvers == null) {
/** */
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
通过 getDefaultArgumentResolvers()办法,能够看到 Spring 为咱们提供了哪些默认的参数解析器,这些解析器都是 HandlerMethodArgumentResolver 接口的实现类。
针对不同的参数类型,Spring 提供了一些根底的参数解析器,其中有基于注解的解析器,也有基于特定类型的解析器,当然也有兜底默认的解析器,如果已有的解析器不能满足解析要求,Spring 也提供了反对用户自定义解析器的扩大点,源码如下:
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDefaultArgumentResolvers
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
// Annotation-based argument resolution 基于注解
/** @RequestPart 文件注入 */
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
/** @RequestParam 名称解析参数 */
resolvers.add(new RequestParamMapMethodArgumentResolver());
/** @PathVariable url 门路参数 */
resolvers.add(new PathVariableMethodArgumentResolver());
/** @PathVariable url 门路参数,返回一个 map */
resolvers.add(new PathVariableMapMethodArgumentResolver());
/** @MatrixVariable url 矩阵变量参数 */
resolvers.add(new MatrixVariableMethodArgumentResolver());
/** @MatrixVariable url 矩阵变量参数 返回一个 map*/
resolvers.add(new Matrix VariableMapMethodArgumentResolver());
/** 兜底解决 @ModelAttribute 注解和无注解 */
resolvers.add(new ServletModelAttributeMethodProcessor(false));
/** @RequestBody body 体解析参数 */
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
/** @RequestPart 应用相似 RequestParam */
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
/** @RequestHeader 解析申请 header */
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
/** @RequestHeader 解析申请 header,返回 map */
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
/** Cookie 中取值注入 */
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
/** @Value */
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
/** @SessionAttribute */
resolvers.add(new SessionAttributeMethodArgumentResolver());
/** @RequestAttribute */
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution 基于类型
/** Servlet api 对象 HttpServletRequest 对象绑定值 */
resolvers.add(new ServletRequestMethodArgumentResolver());
/** Servlet api 对象 HttpServletResponse 对象绑定值 */
resolvers.add(new ServletResponseMethodArgumentResolver());
/** http 申请中 HttpEntity RequestEntity 数据绑定 */
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
/** 申请重定向 */
resolvers.add(new RedirectAttributesMethodArgumentResolver());
/** 返回 Model 对象 */
resolvers.add(new ModelMethodProcessor());
/** 解决入参,返回一个 map */
resolvers.add(new MapMethodProcessor());
/** 处理错误办法参数,返回最初一个对象 */
resolvers.add(new ErrorsMethodArgumentResolver());
/** SessionStatus */
resolvers.add(new SessionStatusMethodArgumentResolver());
/** */
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
// Custom arguments 用户自定义
if (getCustomArgumentResolvers() != null) {resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all 兜底默认
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
HandlerMethodArgumentResolver接口中只定义了两个办法,别离是解析器适用范围确定办法 supportsParameter()和参数解析办法 resolveArgument(),不同用处的参数解析器的应用差别就体现在这两个办法上,这里就不具体开展参数的解析和绑定过程。
3. 自定义参数解析器的设计
Spring 的设计很好践行了开闭准则,不仅在封装整合了很多十分弱小的能力,也为用户留好了自定义拓展的能力,参数解析器也是这样,Spring 提供的参数解析器根本能满足罕用的参数解析能力,但很多零碎的参数传递并不标准,比方京东 color 网关传业务参数都是封装在 body 中,须要先从 body 中取出业务参数,而后再针对性解析,这时候 Spring 提供的解析器就帮不了咱们了,须要咱们扩大自定义适配参数解析器了。
Spring 提供两种自定义参数解析器的形式,一种是实现适配器接口 HandlerMethodArgumentResolver,另一种是继承已有的参数解析器(HandlerMethodArgumentResolver 接口的现有实现类)例如 AbstractNamedValueMethodArgumentResolver 进行加强优化。如果是深度定制化的自定义参数解析器,倡议实现本人实现接口进行开发,以实现接口适配器接口自定义开发解析器为例,介绍如何自定义一个参数解析器。
通过查看源码发现,参数解析适配器接口留给我扩大的办法有两个,别离是 supportsParameter()和 resolveArgument(),第一个办法是自定义参数解析器实用的场景,也就是如何命中参数解析器,第二个是具体解析参数的实现。
public interface HandlerMethodArgumentResolver {
/**
* 辨认到哪些参数特色,才应用以后自定义解析器
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 具体参数解析办法
*/
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
当初开始具体实现一个基于注解的自定义参数解析器,这个是代码理论应用过程中用到的参数解析器,获取 color 网关的 body 业务参数,而后解析后给 Controller 办法间接应用。
public class ActMethodArgumentResolver implements HandlerMethodArgumentResolver {
private static final String DEFAULT_VALUE = "body";
@Override
public boolean supportsParameter(MethodParameter parameter) {
/** 只有指定注解正文的参数才会走以后自定义参数解析器 */
return parameter.hasParameterAnnotation(RequestJsonParam.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
/** 获取参数注解 */
RequestJsonParam attribute = parameter.getParameterAnnotation(RequestJsonParam.class);
/** 获取参数名 */
String name = attribute.value();
/** 获取指定名字参数的值 */
String value = webRequest.getParameter(StringUtils.isEmpty(name) ? DEFAULT_VALUE : name);
/** 获取注解设定参数类型 */
Class<?> targetParamType = attribute.recordClass();
/** 获取理论参数类型 */
Class<?> webParamType = parameter.getParameterType()
/** 以自定义参数类型为准 */
Class<?> paramType = targetParamType != null ? targetParamType : parameter.getParameterType();
if (ObjectUtils.equals(paramType, String.class)
|| ObjectUtils.equals(paramType, Integer.class)
|| ObjectUtils.equals(paramType, Long.class)
|| ObjectUtils.equals(paramType, Boolean.class)) {JSONObject object = JSON.parseObject(value);
log.error("ActMethodArgumentResolver resolveArgument,paramName:{}, object:{}", paramName, JSON.toJSONString(object));
if (object.get(paramName) instanceof Integer && ObjectUtils.equals(paramType, Long.class)) {
// 入参:Integer 指标类型:Long
result = paramType.cast(((Integer) object.get(paramName)).longValue());
}else if (object.get(paramName) instanceof Integer && ObjectUtils.equals(paramType, String.class)) {
// 入参:Integer 指标类型:String
result = String.valueOf(object.get(paramName));
}else if (object.get(paramName) instanceof Long && ObjectUtils.equals(paramType, Integer.class)) {
// 入参:Long 指标类型:Integer(精度失落)result = paramType.cast(((Long) object.get(paramName)).intValue());
}else if (object.get(paramName) instanceof Long && ObjectUtils.equals(paramType, String.class)) {
// 入参:Long 指标类型:String
result = String.valueOf(object.get(paramName));
}else if (object.get(paramName) instanceof String && ObjectUtils.equals(paramType, Long.class)) {
// 入参:String 指标类型:Long
result = Long.valueOf((String) object.get(paramName));
} else if (object.get(paramName) instanceof String && ObjectUtils.equals(paramType, Integer.class)) {
// 入参:String 指标类型:Integer
result = Integer.valueOf((String) object.get(paramName));
} else {result = paramType.cast(object.get(paramName));
}
}else if (paramType.isArray()) {
/** 入参是数组 */
result = JsonHelper.fromJson(value, paramType);
if (result != null) {Object[] targets = (Object[]) result;
for (int i = 0; i < targets.length; i++) {WebDataBinder binder = binderFactory.createBinder(webRequest, targets[i], name + "[" + i + "]");
validateIfApplicable(binder, parameter, annotations);
}
}
} else if (Collection.class.isAssignableFrom(paramType)) {
/** 这里要特地留神!!!,汇合参数因为范型获取不到汇合元素类型,所以指定类型就十分要害了 */
Class recordClass = attribute.recordClass() == null ? LinkedHashMap.class : attribute.recordClass();
result = JsonHelper.fromJsonArrayBy(value, recordClass, paramType);
if (result != null) {Collection<Object> targets = (Collection<Object>) result;
int index = 0;
for (Object targetObj : targets) {WebDataBinder binder = binderFactory.createBinder(webRequest, targetObj, name + "[" + (index++) + "]");
validateIfApplicable(binder, parameter, annotations);
}
}
} else{result = JSON.parseObject(value, paramType);
}
if (result != null) {
/** 参数绑定 */
WebDataBinder binder = binderFactory.createBinder(webRequest, result, name);
result = binder.convertIfNecessary(result, paramType, parameter);
validateIfApplicable(binder, parameter, annotations);
mavContainer.addAttribute(name, result);
}
}
自定义参数解析器注解的定义如下,这里定义了一个比拟非凡的属性 recordClass,后续会讲到是解决什么问题。
/**
* 申请 json 参数解决注解
* @author wangpengchao01
* @date 2022-11-07 14:18
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestJsonParam {
/**
* 绑定的申请参数名
*/
String value() default "body";
/**
* 参数是否必须
*/
boolean required() default false;
/**
* 默认值
*/
String defaultValue() default ValueConstants.DEFAULT_NONE;
/**
* 汇合 json 反序列化后记录的类型
*/
Class recordClass() default null;}
通过配置类将自定义解析器注册到 Spring 容器中
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public static ActMethodArgumentResolver actMethodArgumentResolverConfigurer() {return new ActMethodArgumentResolver();
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {argumentResolvers.add(actMethodArgumentResolverConfigurer());
}
}
到此,一个残缺的基于注解的自定义参数解析器就实现了。
4. 总结
理解 Spring 的参数解析器原理有助于正确应用 Spring 的参数解析器,也让咱们能够设计实用于本身零碎的参数解析器,对于一些通用参数类型的解析缩小反复代码的书写,然而这里有个前提是咱们 我的项目中简单类型的入参要对立 , 前端传递参数的格局也要对立,不然设计自定义参数解析器就是个劫难,须要做各种简单的兼容工作。参数解析器的设计尽量要放在我的项目开发开始阶段,历史简单的零碎如果接口开发没有对立标准也不倡议自定义参数解析器设计。
该文章仅作为 Spring 参数解析器的介绍性解读,心愿对大家有所帮忙,欢送有这类需要或者趣味的同学沟通交流,批评指正,一起提高!