背景前段时间开发一个接口, 因为调用我接口的同事脾气特别好, 我也就不客气, 我就直接把源代码发给他当接口定义了.
没想到同事看到我的代码问: 要么 get a,b,c 要么 post [a,b,c]. 这么写可以自动解析? 他们一直都是自己转换成 list.
我很肯定的说可以, 但是已经习惯这么用了, 没有了解底层的机制, 这里其实 RequestParam 这个注解是不能省略的, 普通的字符串参数可以自动绑定, 需要这种内部转换的不可以.
参数绑定原理
Spring 的参数解析使用 HandlerMethodArgmentResolver 类型的组件完成. 不同类型的使用不同的 ArgumentResolver 来解析. 具体参考 RequestMappingHandlerAdapter 类的源码. 里面有个方法是很好的诠释:
- // 获取默认的 HandlerMethodArgumentResolver
- private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
- List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
- // 1. 基于注解的参数解析 <-- 解析的数据来源主要是 HttpServletRequest | ModelAndViewContainer
- // Annotation-based argument resolution
- // 解析被注解 @RequestParam, @RequestPart 修饰的参数, 数据的获取通过 HttpServletRequest.getParameterValues
- resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
- // 解析被注解 @RequestParam 修饰, 且类型是 Map 的参数, 数据的获取通过 HttpServletRequest.getParameterMap
- resolvers.add(new RequestParamMapMethodArgumentResolver());
- // 解析被注解 @PathVariable 修饰, 数据的获取通过 uriTemplateVars, 而 uriTemplateVars 却是通过 RequestMappingInfoHandlerMapping.handleMatch 生成, 其实就是 uri 中映射出的 key <-> value
- resolvers.add(new PathVariableMethodArgumentResolver());
- // 解析被注解 @PathVariable 修饰 且数据类型是 Map, 数据的获取通过 uriTemplateVars, 而 uriTemplateVars 却是通过 RequestMappingInfoHandlerMapping.handleMatch 生成, 其实就是 uri 中映射出的 key <-> value
- resolvers.add(new PathVariableMapMethodArgumentResolver());
- // 解析被注解 @MatrixVariable 修饰, 数据的获取通过 URI 提取了; 后存储的 uri template 变量值
- resolvers.add(new MatrixVariableMethodArgumentResolver());
- // 解析被注解 @MatrixVariable 修饰 且数据类型是 Map, 数据的获取通过 URI 提取了; 后存储的 uri template 变量值
- resolvers.add(new MatrixVariableMapMethodArgumentResolver());
- // 解析被注解 @ModelAttribute 修饰, 且类型是 Map 的参数, 数据的获取通过 ModelAndViewContainer 获取, 通过 DataBinder 进行绑定
- resolvers.add(new ServletModelAttributeMethodProcessor(false));
- // 解析被注解 @RequestBody 修饰的参数, 以及被 @ResponseBody 修饰的返回值, 数据的获取通过 HttpServletRequest 获取, 根据 MediaType 通过 HttpMessageConverter 转换成对应的格式, 在处理返回值时 也是通过 MediaType 选择合适 HttpMessageConverter, 进行转换格式, 并输出
- resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
- // 解析被注解 @RequestPart 修饰, 数据的获取通过 HttpServletRequest.getParts()
- resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
- // 解析被注解 @RequestHeader 修饰, 数据的获取通过 HttpServletRequest.getHeaderValues()
- resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
- // 解析被注解 @RequestHeader 修饰且参数类型是 Map, 数据的获取通过 HttpServletRequest.getHeaderValues()
- resolvers.add(new RequestHeaderMapMethodArgumentResolver());
- // 解析被注解 @CookieValue 修饰, 数据的获取通过 HttpServletRequest.getCookies()
- resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
- // 解析被注解 @Value 修饰, 数据在这里没有解析
- resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
- // 解析被注解 @SessionAttribute 修饰, 数据的获取通过 HttpServletRequest.getAttribute(name, RequestAttributes.SCOPE_SESSION)
- resolvers.add(new SessionAttributeMethodArgumentResolver());
- // 解析被注解 @RequestAttribute 修饰, 数据的获取通过 HttpServletRequest.getAttribute(name, RequestAttributes.SCOPE_REQUEST)
- resolvers.add(new RequestAttributeMethodArgumentResolver());
- // 2. 基于类型的参数解析器
- // Type-based argument resolution
- // 解析固定类型参数 (比如: ServletRequest, HttpSession, InputStream 等), 参数的数据获取还是通过 HttpServletRequest
- resolvers.add(new ServletRequestMethodArgumentResolver());
- // 解析固定类型参数 (比如: ServletResponse, OutputStream 等), 参数的数据获取还是通过 HttpServletResponse
- resolvers.add(new ServletResponseMethodArgumentResolver());
- // 解析固定类型参数 (比如: HttpEntity, RequestEntity 等), 参数的数据获取还是通过 HttpServletRequest
- resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
- // 解析固定类型参数 (比如: RedirectAttributes), 参数的数据获取还是通过 HttpServletResponse
- resolvers.add(new RedirectAttributesMethodArgumentResolver());
- // 解析固定类型参数 (比如: Model 等), 参数的数据获取通过 ModelAndViewContainer
- resolvers.add(new ModelMethodProcessor());
- // 解析固定类型参数 (比如: Model 等), 参数的数据获取通过 ModelAndViewContainer
- resolvers.add(new MapMethodProcessor());
- // 解析固定类型参数 (比如: Errors), 参数的数据获取通过 ModelAndViewContainer
- resolvers.add(new ErrorsMethodArgumentResolver());
- // 解析固定类型参数 (比如: SessionStatus), 参数的数据获取通过 ModelAndViewContainer
- resolvers.add(new SessionStatusMethodArgumentResolver());
- // 解析固定类型参数 (比如: UriComponentsBuilder), 参数的数据获取通过 HttpServletRequest
- resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
- // 3. 自定义参数解析器
- // Custom arguments
- if (getCustomArgumentResolvers() != null) {
- resolvers.addAll(getCustomArgumentResolvers());
- }
- // Catch-all
- // 这两个解析器可以解析所有类型的参数
- resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
- resolvers.add(new ServletModelAttributeMethodProcessor(true));
- return resolvers;
- }
在一步步跟踪源码之后, 最终在 PropertyEditorRegistrySupport 这个类中, 有一个 createDefaultEditors 的私有方法. 里面定义了各种类型转换:
- private void createDefaultEditors() {
- this.defaultEditors = new HashMap(64);
- this.defaultEditors.put(Charset.class, new CharsetEditor());
- this.defaultEditors.put(Class.class, new ClassEditor());
- this.defaultEditors.put(Class[].class, new ClassArrayEditor());
- this.defaultEditors.put(Currency.class, new CurrencyEditor());
- this.defaultEditors.put(File.class, new FileEditor());
- this.defaultEditors.put(InputStream.class, new InputStreamEditor());
- this.defaultEditors.put(InputSource.class, new InputSourceEditor());
- this.defaultEditors.put(Locale.class, new LocaleEditor());
- if(pathClass != null) {
- this.defaultEditors.put(pathClass, new PathEditor());
- }
- this.defaultEditors.put(Pattern.class, new PatternEditor());
- this.defaultEditors.put(Properties.class, new PropertiesEditor());
- this.defaultEditors.put(Reader.class, new ReaderEditor());
- this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
- this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
- this.defaultEditors.put(URI.class, new URIEditor());
- this.defaultEditors.put(URL.class, new URLEditor());
- this.defaultEditors.put(UUID.class, new UUIDEditor());
- if(zoneIdClass != null) {
- this.defaultEditors.put(zoneIdClass, new ZoneIdEditor());
- }
- this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
- this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
- this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
- this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
- this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));
- this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
- this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());
- this.defaultEditors.put(Character.TYPE, new CharacterEditor(false));
- this.defaultEditors.put(Character.class, new CharacterEditor(true));
- this.defaultEditors.put(Boolean.TYPE, new CustomBooleanEditor(false));
- this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
- this.defaultEditors.put(Byte.TYPE, new CustomNumberEditor(Byte.class, false));
- this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
- this.defaultEditors.put(Short.TYPE, new CustomNumberEditor(Short.class, false));
- this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
- this.defaultEditors.put(Integer.TYPE, new CustomNumberEditor(Integer.class, false));
- this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
- this.defaultEditors.put(Long.TYPE, new CustomNumberEditor(Long.class, false));
- this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
- this.defaultEditors.put(Float.TYPE, new CustomNumberEditor(Float.class, false));
- this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
- this.defaultEditors.put(Double.TYPE, new CustomNumberEditor(Double.class, false));
- this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
- this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
- this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));
- if(this.configValueEditorsActive) {
- StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
- this.defaultEditors.put(String[].class, sae);
- this.defaultEditors.put(short[].class, sae);
- this.defaultEditors.put(int[].class, sae);
- this.defaultEditors.put(long[].class, sae);
- }
- }
从上面的方法里就可以知道都默认支持哪些类型的自动换换了. 中间过程的源码不一一贴了.
总结一下参数解析绑定的过程
1.SpringMVC 初始化时, RequestMappingHanderAdapter 类会把一些默认的参数解析器添加到 argumentResolvers 中. 当 SpringMVC 接收到请求后首先根据 url 查找对应的 HandlerMethod.
2. 遍历 HandlerMethod 的 MethodParameter 数组.
3. 根据 MethodParameter 的类型来查找确认使用哪个 HandlerMethodArgumentResolver.
4. 解析参数, 从请求中解析出 MethodParameter 对应的参数, 结果都是字符串.
5. 转换参数, 在 DataBinder 时 PropertyEditorRegistrySupport 把 String 转换成具体方法所需要的类型, 这里就包括了基本类型, 对象, List 等.
来源: https://www.cnblogs.com/xiexj/p/11332525.html