SpringMVC源码总结(三)mvc:annotation-driven和mvc:message-converters简单介绍

上一篇文章讲述了最简单的mvc:annotation-driven,此次就要说说@ResponseBody注解,很明显这个注解就是将方法的返回值做为reponse的body部分。咱们进一步分析下这个过程涉及到的内容,首先就是方法返回的类型,能够是字节数组、字符串、对象引用等,将这些返回类型以什么样的内容格式(即response的content-type类型,同时还要考虑到客户端是否接受这个类型)存进response的body中返回给客户端是一个问题,对于这个过程的处理都是靠许许多多的HttpMessageConverter转换器来完成的,这即是本章要讲的内容。 

经常使用的content-type类型有:text/html、text/plain、text/xml、application/json、application/x-www-form-urlencoded、image/png等,不一样的类型,对body中的数据的解析也是不同的。 

咱们的@ResponseBody能够指定content-type,打开ResponseBody注释,咱们能够看到这两个属性consumes和produces,它们就是用来指定request的content-type和response的content-type的。均可以接收一个或者多个,用法注释中已经给出了说明。
 
Java代码   收藏代码
  1. /** 
  2.      * The consumable media types of the mapped request, narrowing the primary mapping. 
  3.      * <p>The format is a single media type or a sequence of media types, 
  4.      * with a request only mapped if the {@code Content-Type} matches one of these media types. 
  5.      * Examples: 
  6.      * <pre class="code"> 
  7.      * consumes = "text/plain" 
  8.      * consumes = {"text/plain", "application/*"} 
  9.      * </pre> 
  10.      * Expressions can be negated by using the "!" operator, as in "!text/plain", which matches 
  11.      * all requests with a {@code Content-Type} other than "text/plain". 
  12.      * <p><b>Supported at the type level as well as at the method level!</b> 
  13.      * When used at the type level, all method-level mappings override 
  14.      * this consumes restriction. 
  15.      * @see org.springframework.http.MediaType 
  16.      * @see javax.servlet.http.HttpServletRequest#getContentType() 
  17.      */  
  18.     String[] consumes() default {};  
  19.   
  20.     /** 
  21.      * The producible media types of the mapped request, narrowing the primary mapping. 
  22.      * <p>The format is a single media type or a sequence of media types, 
  23.      * with a request only mapped if the {@code Accept} matches one of these media types. 
  24.      * Examples: 
  25.      * <pre class="code"> 
  26.      * produces = "text/plain" 
  27.      * produces = {"text/plain", "application/*"} 
  28.      * </pre> 
  29.      * Expressions can be negated by using the "!" operator, as in "!text/plain", which matches 
  30.      * all requests with a {@code Accept} other than "text/plain". 
  31.      * <p><b>Supported at the type level as well as at the method level!</b> 
  32.      * When used at the type level, all method-level mappings override 
  33.      * this consumes restriction. 
  34.      * @see org.springframework.http.MediaType 
  35.      */  
  36.     String[] produces() default {};  

当request的content-type不在consumes指定的范围内,则这个request就不会匹配到这个方法。produces 同时指定了方法的返回值将以什么样的content-type写入response的body中。若是这个属性进行了配置下文在获取服务器端指定的content-type就是所配置的值,不然则会获取默认的全部content-type 
当咱们对@ResponseBody什么都没有配置时,SpringMVC便启用默认的策略帮咱们自动寻找一种最佳的方式将方法的返回值写入response的body中。 

接下来,咱们就须要探究SpringMVC是如何处理这一过程的。先说说个人方式,就是调试,当方法执行完返回后,看DispatcherServlet的doDispatch方法的代码:
 
Java代码   收藏代码
  1. // Determine handler adapter for the current request.  
  2.                 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  
  3.   
  4.                 // Process last-modified header, if supported by the handler.  
  5.                 String method = request.getMethod();  
  6.                 boolean isGet = "GET".equals(method);  
  7.                 if (isGet || "HEAD".equals(method)) {  
  8.                     long lastModified = ha.getLastModified(request, mappedHandler.getHandler());  
  9.                     if (logger.isDebugEnabled()) {  
  10.                         logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);  
  11.                     }  
  12.                     if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {  
  13.                         return;  
  14.                     }  
  15.                 }  
  16.   
  17.                 if (!mappedHandler.applyPreHandle(processedRequest, response)) {  
  18.                     return;  
  19.                 }  
  20.   
  21.                 try {  
  22.    //这里就是执行咱们的业务逻辑,而且对返回结果进行处理的地方  
  23.                     // Actually invoke the handler.  
  24.                     mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  
  25.                 }  

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());这里是适配器进行调度咱们的handler地方,因为咱们使用的是注解,因此对应的适配器是RequestMappingHandlerAdapter,经过一步步的函数调用,最终找到咱们的关注重点到RequestMappingHandlerAdapter的方法invokeHandleMethod,具体实现逻辑: 
Java代码   收藏代码
  1. private ModelAndView invokeHandleMethod(HttpServletRequest request,  
  2.             HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {  
  3.   
  4.         ServletWebRequest webRequest = new ServletWebRequest(request, response);  
  5.   
  6.         WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);  
  7.         ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);  
  8.         ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);  
  9.   
  10.         ModelAndViewContainer mavContainer = new ModelAndViewContainer();  
  11.         mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));  
  12.         modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);  
  13.         mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);  
  14.   
  15.         AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);  
  16.         asyncWebRequest.setTimeout(this.asyncRequestTimeout);  
  17.   
  18.         final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);  
  19.         asyncManager.setTaskExecutor(this.taskExecutor);  
  20.         asyncManager.setAsyncWebRequest(asyncWebRequest);  
  21.         asyncManager.registerCallableInterceptors(this.callableInterceptors);  
  22.         asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);  
  23.   
  24.         if (asyncManager.hasConcurrentResult()) {  
  25.             Object result = asyncManager.getConcurrentResult();  
  26.             mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];  
  27.             asyncManager.clearConcurrentResult();  
  28.   
  29.             if (logger.isDebugEnabled()) {  
  30.                 logger.debug("Found concurrent result value [" + result + "]");  
  31.             }  
  32.             requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);  
  33.         }  
  34.   
  35. //这里是重点,执行handler的业务逻辑,对于@ResponseBody分支的处理在这里  
  36.         requestMappingMethod.invokeAndHandle(webRequest, mavContainer);  
  37.   
  38.         if (asyncManager.isConcurrentHandlingStarted()) {  
  39.             return null;  
  40.         }  
  41. //这里即是分水岭,要么返回一个ModelAndView,对于@ResponseBody的返回内容已写进response的body中,在这里要返回null。  
  42.         return getModelAndView(mavContainer, modelFactory, webRequest);  
  43.     }  

继续深刻看下这个requestMappingMethod.invokeAndHandle方法: 
Java代码   收藏代码
  1. /** 
  2.      * Invokes the method and handles the return value through a registered 
  3.      * {@link HandlerMethodReturnValueHandler}. 
  4.      * 
  5.      * @param webRequest the current request 
  6.      * @param mavContainer the ModelAndViewContainer for this request 
  7.      * @param providedArgs "given" arguments matched by type, not resolved 
  8.      */  
  9.     public final void invokeAndHandle(ServletWebRequest webRequest,  
  10.             ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {  
  11. //这里执行完方法体,并返回结果内容  
  12.         Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);  
  13.   
  14.         setResponseStatus(webRequest);  
  15.   
  16.         if (returnValue == null) {  
  17.             if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {  
  18.                 mavContainer.setRequestHandled(true);  
  19.                 return;  
  20.             }  
  21.         }  
  22.         else if (StringUtils.hasText(this.responseReason)) {  
  23.             mavContainer.setRequestHandled(true);  
  24.             return;  
  25.         }  
  26.   
  27.         mavContainer.setRequestHandled(false);  
  28.   
  29.         try {  
  30. //重点在这里  
  31.             this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);  
  32.         }  
  33.         catch (Exception ex) {  
  34.             if (logger.isTraceEnabled()) {  
  35.                 logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);  
  36.             }  
  37.             throw ex;  
  38.         }  
  39.     }  

mavContainer是ModelAndViewContainer类型,主要存储着model信息和view信息,它的一个属性requestHandled为true表示response直接处理不须要view的解决方案(便是须要返回一个视图的)。这里的mavContainer.setRequestHandled(false)只是初始时默认采用view的解决方案。 
继续看this.returnValueHandlers.handleReturnValue具体内容:
 
Java代码   收藏代码
  1. /**这里已经写了,要遍历已经注册的HandlerMethodReturnValueHandler,而后执行那个支持returnValue的那一个HandlerMethodReturnValueHandler 
  2.      * Iterate over registered {@link HandlerMethodReturnValueHandler}s and invoke the one that supports it. 
  3.      * @exception IllegalStateException if no suitable {@link HandlerMethodReturnValueHandler} is found. 
  4.      */  
  5.     @Override  
  6.     public void handleReturnValue(  
  7.             Object returnValue, MethodParameter returnType,  
  8.             ModelAndViewContainer mavContainer, NativeWebRequest webRequest)  
  9.             throws Exception {  
  10.   
  11.         HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);  
  12.         Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]");  
  13.         handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);  
  14.     }  

继续看下它是如何找到合适的HandlerMethodReturnValueHandler的 
Java代码   收藏代码
  1. /** 
  2.      * Find a registered {@link HandlerMethodReturnValueHandler} that supports the given return type. 
  3.      */  
  4.     private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {  
  5.         for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {  
  6.             if (logger.isTraceEnabled()) {  
  7.                 logger.trace("Testing if return value handler [" + returnValueHandler + "] supports [" +  
  8.                         returnType.getGenericParameterType() + "]");  
  9.             }  
  10.             if (returnValueHandler.supportsReturnType(returnType)) {  
  11.                 return returnValueHandler;  
  12.             }  
  13.         }  
  14.         return null;  
  15.     }  

遍历全部的已注册的HandlerMethodReturnValueHandler,而后调用他们的supportsReturnType方法来判断他们各自是否支持这个返回值类型,经过调试发现会有13个HandlerMethodReturnValueHandler,以后再说这些数据是在何时哪一个地方注册的。列举下经常使用的: 

  • ModelAndViewMethodReturnValueHandler:支持返回值是ModelAndView类型的 
    ModelMethodProcessor:支持返回值是Model的 
    ViewMethodReturnValueHandler:支持返回值是View 
    HttpEntityMethodProcessor:支持返回值是HttpEntity 
    RequestResponseBodyMethodProcess:支持类上或者方法上含有@ResponseBody注解的 
    ViewNameMethodReturnValueHandler:支持返回类型是void或者String 

因此咱们想扩展的话,就能够自定实现一个HandlerMethodReturnValueHandler,而后在初始化时注册进去(这个过程后面再说)。言归正转,对于本工程RequestResponseBodyMethodProcess是支持的,因此它将被做为HandlerMethodReturnValueHandler返回,继续执行上面的handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest)方法,咱们来看下RequestResponseBodyMethodProcess具体的处理过程: 

Java代码   收藏代码
  1. @Override  
  2.     public void handleReturnValue(Object returnValue, MethodParameter returnType,  
  3.             ModelAndViewContainer mavContainer, NativeWebRequest webRequest)  
  4.             throws IOException, HttpMediaTypeNotAcceptableException {  
  5. //走到这一步,说明该方法不须要view的方案,因此要将requestHandled标示置为true,供其余注释使用判断当前方法的返回值处理策略  
  6.         mavContainer.setRequestHandled(true);  
  7.         if (returnValue != null) {  
  8.             writeWithMessageConverters(returnValue, returnType, webRequest);  
  9.         }  
  10.     }  

方法writeWithMessageConverters的具体内容为: 
Java代码   收藏代码
  1. protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,  
  2.             ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)  
  3.             throws IOException, HttpMediaTypeNotAcceptableException {  
  4.   
  5.         Class<?> returnValueClass = returnValue.getClass();  
  6.         HttpServletRequest servletRequest = inputMessage.getServletRequest();  
  7. //获取客户端Accept字段接收的content-type  
  8.         List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);  
  9. //获取服务器端指定的content-type,若是@RequestMapping中的produces配置了content-type,则返回此content-type,若果没有,则获取全部HttpMessageConverter所支持的content-type,而后经过requestedMediaTypes和producibleMediaTypes 对比,选定一个最合适的content-type做为  
  10. //selectedMediaType  
  11.         List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);  
  12.   
  13.         Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();  
  14.         for (MediaType requestedType : requestedMediaTypes) {  
  15.             for (MediaType producibleType : producibleMediaTypes) {  
  16.                 if (requestedType.isCompatibleWith(producibleType)) {  
  17.                     compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));  
  18.                 }  
  19.             }  
  20.         }  
  21.         if (compatibleMediaTypes.isEmpty()) {  
  22.             throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);  
  23.         }  
  24.   
  25.         List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);  
  26.         MediaType.sortBySpecificityAndQuality(mediaTypes);  
  27.   
  28.         MediaType selectedMediaType = null;  
  29.         for (MediaType mediaType : mediaTypes) {  
  30.             if (mediaType.isConcrete()) {  
  31.                 selectedMediaType = mediaType;  
  32.                 break;  
  33.             }  
  34.             else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {  
  35.                 selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;  
  36.                 break;  
  37.             }  
  38.         }  
  39.   
  40.         if (selectedMediaType != null) {  
  41.             selectedMediaType = selectedMediaType.removeQualityValue();  
  42.             for (HttpMessageConverter<?> messageConverter :   
  43. //遍历全部已注册的HttpMessageConverter,选出一个支持返回值类型returnValueClass和  
  44. //selectedMediaType的HttpMessageConverter来进行写入数据到response的body中。  
  45. this.messageConverters) {  
  46.                 if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {  
  47.                     ((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);  
  48.                     if (logger.isDebugEnabled()) {  
  49.                         logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +  
  50.                                 messageConverter + "]");  
  51.                     }  
  52.                     return;  
  53.                 }  
  54.             }  
  55.         }  
  56.         throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);  
  57.     }  

获取客户端的content-type,只需解析Accept头字段便可,获取服务器端指定的content-type则分两种状况,第一种状况为:你在@RequestMapping中指定了produces的content-type类型(会将这一信息存进request的属性中,属性名为HandlerMapping接口名+'.producibleMediaTypes')若是没指定,则第二种状况:获取全部的已注册的messageConverter,获取它们全部的支持的content-type类型,而且过滤掉那些不支持returnValueClass的类型。而后在这两组List<MediaType> requestedMediaTypes和producibleMediaTypes中进行比较匹配(这里的比较规则也挺多的,涉及到q值,有兴趣大家能够总结下),选出一个最合适的content-type,至此有了返回值类型returnValueClass和要写进reponseBody的content-type类型,而后就是要找到一个支持这二者的HttpMessageConverter,已注册的HttpMessageConverter以下: 

  • ByteArrayHttpMessageConverter:支持返回值类型为byte[],content-type为application/octet-stream,*/* 

    StringHttpMessageConverter:支持的返回值类型为String,content-type为 text/plain;charset=ISO-8859-1,*/* 

    ResourceHttpMessageConverter:支持的返回值类型为Resource,content-type为 */* 

    SourceHttpMessageConverter:支持的返回值类型为DomSource,SAXSource,Source,StreamSource,content-type为application/xml,text/xml,application/*+xml 

    MappingJacksonHttpMessageConverter:判断返回值可否被格式化成json,content-type为 application/json,application/*+json 

    AllEncompassingFormHttpMessageConverter:支持的返回值类型为MultiValueMap,content-type为application/x-www-form-urlencoded,multipart/form-data 

对于咱们的工程来讲,返回类型为String,选出来的最合适的content-type是text/html,而且StringHttpMessageConverter的*/*是兼容任意类型的,因此StringHttpMessageConverter会被选中,而后将返回值以text/html形式写进response的body中。 

顺便说下对于content-length是这样获取的: 
首先从指定的content-type(本工程即text/html)中获取字符集,若能获取到则使用该字符集,若获取不到则使用默认的字符集,对于本工程来讲,text/html不像application/json;charset=utf-8那样含有字符集,因此将会使用StringHttpMessageConverter默认的字符集 
Java代码   收藏代码
  1. public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");  
  2.   
  3.     private final Charset defaultCharset;  
  4.   
  5.     private final List<Charset> availableCharsets;  
  6.   
  7.     private boolean writeAcceptCharset = true;  
  8.   
  9.   
  10.     /** 
  11.      * A default constructor that uses {@code "ISO-8859-1"} as the default charset. 
  12.      * @see #StringHttpMessageConverter(Charset) 
  13.      */  
  14.     public StringHttpMessageConverter() {  
  15.         this(DEFAULT_CHARSET);  
  16.     }  
  17.   
  18.     /** 
  19.      * A constructor accepting a default charset to use if the requested content 
  20.      * type does not specify one. 
  21.      */  
  22.     public StringHttpMessageConverter(Charset defaultCharset) {  
  23.         super(new MediaType("text""plain", defaultCharset), MediaType.ALL);  
  24.         this.defaultCharset = defaultCharset;  
  25.         this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());  
  26.     }  

StringHttpMessageConverter有两个构造函数。当你没有给它指定字符集时,使用默认的ISO-8859-1,这即是形成乱码的一个缘由,因为咱们常用utf-8,因此能够在构造它时指定一下字符集。继续content-length的计算,有了字符集就好办了,计算方法为str.getBytes(字符集).length即是content-length的值,代码以下。 
Java代码   收藏代码
  1. if (headers.getContentLength() == -1) {  
  2.             Long contentLength = getContentLength(t, headers.getContentType());  
  3.             if (contentLength != null) {  
  4.                 headers.setContentLength(contentLength);  
  5.             }  
  6.         }  

 
Java代码   收藏代码
  1. @Override  
  2.     protected Long getContentLength(String s, MediaType contentType) {  
  3.         Charset charset = getContentTypeCharset(contentType);  
  4.         try {  
  5.             return (long) s.getBytes(charset.name()).length;  
  6.         }  
  7.         catch (UnsupportedEncodingException ex) {  
  8.             // should not occur  
  9.             throw new IllegalStateException(ex);  
  10.         }  
  11.     }  

继续说StringHttpMessageConverter的写入过程, 
Java代码   收藏代码
  1. @Override  
  2.     protected void writeInternal(String s, HttpOutputMessage outputMessage) throws IOException {  
  3.         if (this.writeAcceptCharset) {  
  4.             outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());  
  5.         }  
  6.         Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());  
  7.    //重点  
  8.         StreamUtils.copy(s, charset, outputMessage.getBody());  
  9.     }  

上面的charset就是ISO-8859-1,也就是将返回的字符串以ISO-8859-1编码写进response的body中。至此就完成了,而后就出现了中国这种乱码。 
下一篇文章将会详细说说乱码,再下一篇文章还要继续本节遗留的不少问题,第一个HandlerMethodReturnValueHandler的来源及使用以及咱们来自定义一个HandlerMethodReturnValueHandler,第二个问题:其余HttpMessageConverter的使用以及自定义HttpMessageConverter。