在SpringMVC的使用時,每每會用到@RequestBody和@ResponseBody兩個註解,尤爲是處理ajax請求必然要使用@ResponseBody註解。這兩個註解對應着Controller方法的參數解析和返回值處理,開始時都是隻知其用,不知原理。咱們來看個例子。java
@RequestMapping("/requestBody") public void requestBody(@RequestBody String body, Writer writer) throws IOException{ writer.write(body); } @RequestMapping(value="/responseBody", produces="application/json") @ResponseBody public Map<String, Object> responseBody(){ Map<String, Object> retMap = new HashMap<>(); retMap.put("param1", "abc"); return retMap; }
第一個requestBody請求,使用@RequestBody將HTTP請求體轉換成String類型,第二個responseBody請求,將Map對象轉換成json格式輸出到HTTP響應中。這兩個請求方法沒有什麼特殊,就是一個在參數前加了@RequestBody註解,一個在方法上加了@ResponseBody註解。而這兩個註解是怎麼完成HTTP報文信息同Controller方法中對象的轉換的呢?web
SpringMVC處理請求和響應時,支持多種類型的請求參數和返回類型,而此種功能的實現就須要對HTTP消息體和參數及返回值進行轉換,爲此SpringMVC提供了大量的轉換類,全部轉換類都實現了HttpMessageConverter接口。ajax
public interface HttpMessageConverter<T> { // 當前轉換器是否能將HTTP報文轉換爲對象類型 boolean canRead(Class<?> clazz, MediaType mediaType); // 當前轉換器是否能將對象類型轉換爲HTTP報文 boolean canWrite(Class<?> clazz, MediaType mediaType); // 轉換器能支持的HTTP媒體類型 List<MediaType> getSupportedMediaTypes(); // 轉換HTTP報文爲特定類型 T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; // 將特定類型對象轉換爲HTTP報文 void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
HttpMessageConverter接口定義了5個方法,用於將HTTP請求報文轉換爲java對象,以及將java對象轉換爲HTTP響應報文。json
對應到SpringMVC的Controller方法,read方法便是讀取HTTP請求轉換爲參數對象,write方法便是將返回值對象轉換爲HTTP響應報文。SpringMVC定義了兩個接口來操做這兩個過程:參數解析器HandlerMethodArgumentResolver和返回值處理器HandlerMethodReturnValueHandler。app
// 參數解析器接口 public interface HandlerMethodArgumentResolver { // 解析器是否支持方法參數 boolean supportsParameter(MethodParameter parameter); // 解析HTTP報文中對應的方法參數 Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception; } // 返回值處理器接口 public interface HandlerMethodReturnValueHandler { // 處理器是否支持返回值類型 boolean supportsReturnType(MethodParameter returnType); // 將返回值解析爲HTTP響應報文 void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception; }
參數解析器和返回值處理器在底層處理時,都是經過HttpMessageConverter進行轉換。流程以下:ide
SpringMVC爲@RequestBody和@ResponseBody兩個註解實現了統一處理類RequestResponseBodyMethodProcessor,實現了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler兩個接口。ui
由SpringMVC源碼(四)-請求處理可知,Controller方法被封裝成ServletInvocableHandlerMethod類,而且由invokeAndHandle方法完成請求處理。this
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 執行請求 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // 返回值處理 try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex); } throw ex; } } public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 參數解析 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); // invoke Controller方法 Object returnValue = doInvoke(args); return returnValue; }
在invoke Controller方法的先後分別執行了方法參數的解析和返回值的處理,咱們分別來看。.net
參數解析code
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; // 遍歷全部參數,逐個解析 for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } // 參數解析器解析HTTP報文到參數 if (this.argumentResolvers.supportsParameter(parameter)) { args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } } return args; }
getMethodArgumentValues方法中的argumentResolvers就是多個HandlerMethodArgumentResolver的集合體,supportsParameter方法尋找參數合適的解析器,resolveArgument調用具體解析器的resolveArgument方法執行。
咱們從RequestResponseBodyMethodProcessor看看@RequestBody的解析過程。RequestResponseBodyMethodProcessor的supportsParameter定義了它支持的參數類型,即必須有@RequestBody註解。
public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class); }
再來看resolveArgument方法
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional(); // 經過HttpMessageConverter讀取HTTP報文 Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null) { validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); return adaptArgumentIfNecessary(arg, parameter); }
具體實現由HttpMessageConverter來完成
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { .... try { inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage); for (HttpMessageConverter<?> converter : this.messageConverters) { Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass(); .... // 判斷轉換器是否支持參數類型 if (converter.canRead(targetClass, contentType)) { if (inputMessage.getBody() != null) { inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType); // read方法執行HTTP報文到參數的轉換 body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage); body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType); } else { body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType); } break; } ... } } catch (IOException ex) { throw new HttpMessageNotReadableException("I/O error while reading input message", ex); } .... return body; }
代碼部分省略了,關鍵部分便是遍歷全部的HttpMessageConverter,經過canRead方法判斷轉換器是否支持對參數的轉換,而後執行read方法完成轉換。
返回值處理
完成Controller方法的調用後,在ServletInvocableHandlerMethod的invokeAndHandle中,使用返回值處理器對返回值進行轉換。
this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
這裏的returnValueHandlers也是HandlerMethodReturnValueHandler的集合體HandlerMethodReturnValueHandlerComposite
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { // 選擇合適的HandlerMethodReturnValueHandler HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } // 執行返回值處理 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); }
selectHandler方法遍歷全部HandlerMethodReturnValueHandler,調用其supportsReturnType方法選擇合適的HandlerMethodReturnValueHandler,而後調用其handleReturnValue方法完成處理。
這裏仍是以RequestResponseBodyMethodProcessor來分析下@ResponseBody的處理,它的具體實如今AbstractMessageConverterMethodProcessor抽象基類中。
public boolean supportsReturnType(MethodParameter returnType) { return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class)); }
RequestResponseBodyMethodProcessor要求方法上有@ResponseBody註解或者方法所在的Controller類上有@ResponseBody的註解。這就是經常用@RestController註解代替@Controller註解的緣由,由於@RestController註解自帶@ResponseBody。
handleReturnValue方法實際也是調用HttpMessageConverter來完成轉換處理
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // 調用HttpMessageConverter執行 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); } protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { .... if (selectedMediaType != null) { selectedMediaType = selectedMediaType.removeQualityValue(); for (HttpMessageConverter<?> messageConverter : this.messageConverters) { // 判斷是否支持返回值類型 if (messageConverter.canWrite(valueType, selectedMediaType)) { outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage); if (outputValue != null) { addContentDispositionHeader(inputMessage, outputMessage); // 執行返回值轉換 ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage); ... } return; } } } .... }
使用canWrite方法選擇合適的HttpMessageConverter,而後調用write方法完成轉換。
至此咱們基本走完了一個HTTP請求報文通過處理後到HTTP響應報文的轉換過程。如今你可能有個疑惑,SpringMVC咱們都是開箱即用,這些參數解析器和返回值處理器在哪裏定義的呢?在覈心的HandlerAdapter實現類RequestMappingHandlerAdapter的初始化方法中定義的,具體能夠去看SpringMVC源碼(三)-組件初始化。
而在RequestMappingHandlerAdapter構造時,也同時初始化了衆多的HttpMessageConverter,以支持多樣的轉換需求。
WebMvcConfigurationSupport.java protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) { StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(); stringConverter.setWriteAcceptCharset(false); messageConverters.add(new ByteArrayHttpMessageConverter()); messageConverters.add(stringConverter); messageConverters.add(new ResourceHttpMessageConverter()); messageConverters.add(new SourceHttpMessageConverter<Source>()); messageConverters.add(new AllEncompassingFormHttpMessageConverter()); if (romePresent) { messageConverters.add(new AtomFeedHttpMessageConverter()); messageConverters.add(new RssChannelHttpMessageConverter()); } if (jackson2XmlPresent) { ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build(); messageConverters.add(new MappingJackson2XmlHttpMessageConverter(objectMapper)); } else if (jaxb2Present) { messageConverters.add(new Jaxb2RootElementHttpMessageConverter()); } if (jackson2Present) { ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build(); messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper)); } else if (gsonPresent) { messageConverters.add(new GsonHttpMessageConverter()); } }
對於json或xml的轉換方式,只要引入了jackson的依賴,便可自動發現,並註冊相關的轉換器。
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.9.0</version> </dependency>
如今明白了SpringMVC作到了靈活又便捷的使用方式,其實在內部是作了大量的準備工做的。今天看了一篇文章,也是一樣的道理,目前有些在互聯網的浪潮中一鳴驚人或者一晚上暴富的例子,不少人便會誤覺得快速成功很容易實現,但卻沒有看到這些成功的人每每都付出了幾年甚至數十年的努力和積累,纔能有如今的厚積薄發。但願與全部努力的人的共勉,不驕不躁,不要焦慮!