Spring參數的自解析--還在本身轉換?你out了!

背景前段時間開發一個接口,由於調用我接口的同事脾氣特別好,我也就不客氣,我就直接把源代碼發給他當接口定義了。數組

沒想到同事看到個人代碼問:要麼 get  a,b,c  要麼  post [a,b,c]。這麼寫能夠自動解析?他們一直都是本身轉換成list。app

我很確定的說能夠,可是已經習慣這麼用了,沒有了解底層的機制,這裏其實RequestParam這個註解是不能省略的,普通的字符串參數能夠自動綁定,須要這種內部轉換的不能夠。
參數綁定原理
Spring的參數解析使用HandlerMethodArgmentResolver類型的組件完成。不一樣類型的使用不一樣的ArgumentResolver來解析。具體參考RequestMappingHandlerAdapter類的源碼。裏面有個方法是很好的詮釋:post

// 獲取默認的 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);
        }
    }

 


從上面的方法裏就能夠知道都默認支持哪些類型的自動換換了。中間過程的源碼不一一貼了。

總結一下參數解析綁定的過程ui

1.SpringMVC初始化時,RequestMappingHanderAdapter類會把一些默認的參數解析器添加到argumentResolvers中。當SpringMVC接收到請求後首先根據url查找對應的HandlerMethod。this

2.遍歷HandlerMethod的MethodParameter數組。url

3.根據MethodParameter的類型來查找確認使用哪一個HandlerMethodArgumentResolver。spa

4.解析參數,從請求中解析出MethodParameter對應的參數,結果都是字符串。code

5.轉換參數,在DataBinder時PropertyEditorRegistrySupport把String轉換成具體方法所須要的類型,這裏就包括了基本類型、對象、List等。對象

相關文章
相關標籤/搜索