• Spring参数的自解析--还在自己转换?你out了!


    背景前段时间开发一个接口,因为调用我接口的同事脾气特别好,我也就不客气,我就直接把源代码发给他当接口定义了。

    没想到同事看到我的代码问:要么 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等。

  • 相关阅读:
    Step by step Dynamics CRM 2013安装
    SQL Server 2012 Managed Service Account
    Step by step SQL Server 2012的安装
    Step by step 活动目录中添加一个子域
    Step by step 如何创建一个新森林
    向活动目录中添加一个子域
    活动目录的信任关系
    RAID 概述
    DNS 正向查找与反向查找
    Microsoft Dynamics CRM 2013 and 2011 Update Rollups and Service Packs
  • 原文地址:https://www.cnblogs.com/xiexj/p/11332525.html
Copyright © 2020-2023  润新知