Spring引數的自解析--還在自己轉換?你out了!
阿新 • • 發佈:2019-08-19
背景前段時間開發一個介面,因為呼叫我介面的同事脾氣特別好,我也就不客氣,我就直接把原始碼發給他當介面定義了。
沒想到同事看到我的程式碼問:要麼 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