SpringMvc @ResponseBody
-
二. @Response在最小配置、jackson的jar包情況下,json中包含的日期型別欄位都是以時間戳long型別返回
-
三. Jack序列化物件轉為JSON的限制條件
-
四. @ResponseBody如何工作的
-
五. Spring偏底層記錄.
一. @Response使用條件
1.引入依賴jackson-databind 或者其他型別的json轉換,比如gson、fastjson
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.1</version> </dependency>
2.最小配置,<mvc:annotation-driven/>
最低滿足上面兩個條件,即可在@RequestMapping的方法上添加註解@ResponseBody,將結果用JSON直接返回給客戶端.
二. @Response在最小配置、jackson的jar包情況下,json中包含的日期型別欄位都是以時間戳long型別返回
直接說結論,各位可以儘管測試,使用jackson的情況下,轉換的json日期型別欄位都會以時間戳long型別展示;
jackson最簡單的API使用方式普及下,當然你也可以倒數第二行呼叫 writeValueAsString這樣更加簡單:
public static void main(String[] args) throws IOException { JsonEncoding encoding = JsonEncoding.UTF8; ObjectMapper mapper = new ObjectMapper(); JsonGenerator generator =mapper.getFactory().createGenerator(new File("E:\\home\\1.txt"),encoding); ObjectWriter writer = mapper.writer(); Date date = new Date(); writer.writeValue(generator,date); generator.flush(); }
檢視輸出檔案的資訊: Spring底層就是按照這個API 呼叫方式來生成JSON ,我們沒有對ObjectMapper做任何配置,所以生成日期型別都是返回其時間戳;
SpringMvc 4.3中@ResponseBody時間型別是返回時間戳型別,至於其他版本測試就可以知道是否直接返回時間戳型別;
二.1 Jackson Api層面記錄如何取消這種時間型別生成方式
下面用mapper代替你的new ObjectMapper()
方式一. mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);
說明:mapper configure設定需要在 createGenerator 以及 獲取writer之前才有效!
方式二. mapper.setDateFormat(new SimpleDateFormat("yyyy--MM--dd HH:mm:ss"));
說明:這種方式擴充套件性更好,可以日期自定義格式化,相比較方式一更符合開發需求;
方式三. 實體屬性上標註註解 @JsonFormat
public static void main(String[] args) throws IOException { JsonEncoding encoding = JsonEncoding.UTF8; ObjectMapper mapper = new ObjectMapper(); JsonGenerator generator =mapper.getFactory().createGenerator(new File("E:\\home\\1.txt"),encoding); ObjectWriter writer = mapper.writer(); PrivateMyDate date = new PrivateMyDate(); writer.writeValue(generator,date); generator.flush(); } static class PrivateMyDate{ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") public Date now=new Date(); }
效果圖如下:@JsonFormat是Jackson的,而不是Spring的!
說明:方式三應該是日常開發中最方便的,只需要在實體類上新增@JsonFormat,可以滿足各種型別的日期格式
三. Jack序列化物件轉為JSON的限制條件
三.1 Jackson API使用注意事項點:
之前偷懶, 屬性修飾符、getter方法都沒有寫 , 誤打誤撞發現Jackson丟擲異常
com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class xxx and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
當結合Spring @ResponseBody一起使用,那異常可能就是另外一種表現形式(當然下面這種異常不僅僅可能是這個Jackson Api使用注意事項引起的)
java.lang.IllegalArgumentException: No converter found for return value of type: class demo2.MyDate
三.1.1異常引起原因:比如嘗試序列化Json這樣一個實體類,就會丟擲第一種異常,在Spring就會丟擲第二種異常
public class PrivateMyDate { String name="123"; int age=18; }
三.1.2 異常原因說明: Jackson2預設地序列化成JSON,實體類屬性或者對應屬性getter方法為 public型別,才能夠將該屬性成功轉為Json ; 如果只是少數字段不為public型別,那這些少數字段就不會出現在轉換後的Json中;如果所有欄位都不是public且沒有public的getter方法,就會丟擲上面第一種異常 ;
三.1.3 異常解決方案:
方案一.最直接的方案
如果有權操作實體類,給對應實體類新增標準的getter方法(public型別,jackson預設是標準的)
方案二.全域性級別方案
obejctMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
說明:ANY 代表轉換成JSON時候 FIELD即屬性可以為任意型別,public、protected、default、private型別都可以
方案三.實體級別方案
實體類上標註 @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)即可
三.1.4 Jackson2結合Spring4.x實現全域性級別的@JsonFormat以及 @JsonAutoDetect
自己修改了下原來<mvc:annotation-driven/>達到了全域性級別的效果,不用再在實體類上新增@JsonFormat以及@JsonAutoDetect; 簡單說明下原理:新增了MappingJackson2HttpMessageConverter,自己配置了一個ObjectMapper,其中visibility屬性篇幅較長,如果遇到第一種異常的話可以加上;
<mvc:annotation-driven> <mvc:message-converters> <bean id="mappingJackson" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <bean class="com.fasterxml.jackson.databind.ObjectMapper"> <property name="visibility"> <bean class="com.fasterxml.jackson.databind.introspect.VisibilityChecker$Std"> <constructor-arg name="getter" value="DEFAULT"/><!--getter方法級別的最低修飾符--> <constructor-arg name="isGetter" value="DEFAULT"/> <constructor-arg name="setter" value="DEFAULT"/><!--setter方法級別的最低修飾符--> <constructor-arg name="creator" value="DEFAULT"/><!--構造器級別的最低修飾符--> <constructor-arg name="field" value="ANY"/><!-- 屬性級別的最低修飾符 ANY具體檢視列舉Visibility--> </bean> </property> <property name="dateFormat"> <bean class="java.text.SimpleDateFormat"> <constructor-arg name="pattern" value="yyyy-MM-dd"/> </bean> </property>
<property name="serializationInclusion" value="NON_NULL"/> <!-- 如果不想序列化NULL的欄位,配置這個屬性 --> </bean> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
基本上到這裡,Spring以及Jackson2的配置都已經清楚了,用法也基本瞭解 @Response返回Json給客戶端;這些都是<mvc:annotation-driven/>替我們完成的,下面深入瞭解下一些知識.
四. @ResponseBody如何工作的
先說明下,這章比較無聊,我儘量記錄詳細些,就從呼叫完 Controller @RequestMapping方法開始記錄
四.1 程式碼片段位於 org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
this物件為ServletInvocableHandlerMethod, returnValueHandlers物件為HandlerMethodReturnValueHandlerComposite;returnValueHandlers顧名思義就是方法返回值處理器,
它持有一系列Spring為我們默默註冊地HandlerMethodReturnValueHandler,專門用來針對不同@RequestMapping方法返回值,來決定怎麼返回給客戶端;
比如 ModelAndViewMethodReturnValueHandler用來處理ModelAndView的返回值,而RequestResponseBodyMethodProcessor就是用來處理 標註了@ResponseBody 的返回值;
public void invokeAndHandle(ServletWebRequest webRequest,ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //invokeForRequest反射執行了Controller的業務方法,還包括請求引數繫結 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); //業務方法返回值為空,不進一步判斷了,設定請求處理標誌位為true即可 if (returnValue == null) { if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(this.responseReason)) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); try { //業務方法返回值不為null,判斷如何返回響應 //返回值處理器物件是HandlerMethodReturnValueHandlerComposite 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; } }
四.1.1 程式碼片段位於 org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue
首先挑選上面說到的HandlerMethodReturnValueHandler,這個肯定不能隨意挑選,肯定有條件的挑選,就像相親? 扯遠了,挑選到合適的HandlerMethodReturnValueHandlers,它就知道該怎麼做了,handleReturnValue處理返回值;
public void handleReturnValue(Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); }
四.1.2 先看挑選的條件吧,程式碼位於 org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#selectHandler
this指代HandlerMethodReturnValueHandlerComposite,上面說的Spring默默註冊的HandlerMethodReturnValueHandler就是在returnValueHandlers集合中;遍歷這個集合,挑選的條件就是:supportsReturnType,而returnType只需要知道是 ReturnValueMethodParameter 型別,且持有方法的返回值即可;每個HandlerMethodReturnValueHandler的實現類肯定各自實現了supportsReturnType、以及handleReturnValue,這裡只記錄 RequestResponseBodyMethodProcessor 就是下面會用到的用來解析 @Response 註解的HandlerMethodReturnValueHandler.
private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) { boolean isAsyncValue = isAsyncReturnValue(value, returnType); for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) { if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) { continue; } if (handler.supportsReturnType(returnType)) { return handler; } } return null; }
程式碼片段位於 org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#supportsReturnType
RequestResponseBodyMethodProcessor的supportsReturnType方法,可以看到符合條件是:@ReuqestMapping方法上標註@ResponseBody 或者 @Controller標註@ResponseBody,當然@RestController這種也是包含註解@ResponseBody; 滿足條件就會直接返回這個HandlerMethodReturnValueHandler,然後使用HandlerMethodReturnValueHandler的handleReturnValue.
四.1.3 挑選完畢之後,呼叫handleReturnValue方法;程式碼片段位於 org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue
inputMessage、outputMessage就是封裝了的request以及response物件,最關鍵的步驟在 writeWithMessageConverters.
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); // Try even with null return value. ResponseBodyAdvice could get involved. writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }
四.1.4 程式碼片段位於 org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters
writeWithMessageConverters方法省略無關重要部分後:clazz是@RequestMapping方法返回值型別,返回值為null取得是方法宣告型別,type取方法宣告型別;兩者的區別:clazz可能用@RequestMapping方法method的getReturnType,而type是method的getGenericReturnType. 從inputMessage物件中獲取原生request,並且getAcceptableMediaTypes方法,且看四.1.5 ,根據一定的策略來分析請求的MediaType ; getProducibleMediaTypes方法且看四.1.6, 遍歷請求頭、請求字尾得到的MediaType,以及可以支援寫回的MediaType, isCompatibleWith就是進行相容性判斷. 簡單來說,比如 producibleMediaTypes 是application/json型別的,請求頭或者請求字尾得出來的MediaType得是 application/json或者 */*這樣的,才能叫做相容吧. 相容性的判斷邏輯且看四.1.7 isCompatibleWith . 這裡補充下,如果相容的mediaType是*/*型別的,那就會以application/octet-stream這種形式寫回. 選中了相容的MediaType,後面的分析到四.1.8 記錄.
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { Class<?> clazz = getReturnValueType(value, returnType); Type type = getGenericType(returnType); HttpServletRequest servletRequest = inputMessage.getServletRequest(); List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest); List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, clazz, type); //程式碼略... Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>(); for (MediaType requestedType : requestedMediaTypes) { for (MediaType producibleType : producibleMediaTypes) { if (requestedType.isCompatibleWith(producibleType)) { compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType)); } } } List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes); MediaType.sortBySpecificityAndQuality(mediaTypes); MediaType selectedMediaType = null; for (MediaType mediaType : mediaTypes) { if (mediaType.isConcrete()) { selectedMediaType = mediaType; break; } else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) { selectedMediaType = MediaType.APPLICATION_OCTET_STREAM; break; } } //程式碼上部略.....
四.1.5 程式碼片段位於 org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#getAcceptableMediaTypes
呼叫了ContentNegotiationManager的resolveMediaTypes方法解析request,來判斷請求的MediaType型別.
程式碼片段位於 org.springframework.web.accept.ContentNegotiationManager#resolveMediaTypes
this物件指代ContentNegotiationManager,遍歷其ContentNegotiationStrategy集合strategies, 呼叫介面的resolveMediaTypes來解析MediaType. 如果需要自定義解析請求策略,可以實現該介面ContentNegotiationStrategy. <mvc:annotation-driven/> (spring4.x是這樣) 默默為我們註冊了兩個PathExtensionContentNegotiationStrategy 、HeaderContentNegotiationStrategy,作用分別是用來解析 請求字尾形式、 Http Accept的請求頭.
四.1.6 程式碼片段位於 org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#getProducibleMediaTypes
首先呼叫request請求的getAttribute獲取某些屬性HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE,這個屬性在IDEA通過全域性搜尋,在RequestMappingInfoHandlerMapping中找到了setAttribute它, 這個設定是因為@RequestMapping(produce={xxxxx})這個時候儲存的produce的屬性. this物件指代RequestResponseBodyMethodProcessor,其集合messageConverters也是Spring默默為我們註冊的. allSupportedMediaTypes在RequestResponseBodyMethodProcessor初始化的時候設定上的,當時就是遍歷的messageConverters,呼叫HttpMessageConverter的getSupportedMediaTypes方法,一個個加入到allSupportedMediaTypes中的. 現在又遍歷messageConverters,逐個呼叫canWrite方法,返回true代表符合條件,getSupportMediaTypes得到MediaType的集合,代表可以支援的響應媒體型別 ;
當前messageConverter集合如下:大部分不是GenericHttpMessageConverters型別的,這型別的canWrite(returnValueClass,null)的具體邏輯兩個,一支援返回值型別supports(clazz),二可以寫回null型別媒體型別MediaType; 比如ByteArrayHttpMessageConverter的supports方法就是 byte[].class == clazz,StringHttpMessageConverter的supports方法就是 String.class == clazz ; 另外 Jaxb2RootElementHttpMessageConverter的 supports方法就是 判斷 返回值型別clazz 上面有註解 XmlRootElement ,而 MappingJackson2HttpMessageConverter 呼叫objectMapper.canSerialize方法判斷能否序列化成JSON處理;
程式碼片段位於: org.springframework.http.converter.AbstractHttpMessageConverter#canWrite(java.lang.Class<?>, org.springframework.http.MediaType)
程式碼片段位於: org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#canWrite
程式碼片段位於: org.springframework.http.converter.AbstractHttpMessageConverter#getSupportedMediaTypes
MappingJackson2HttpMessageConverter的getSupportedMediaTypes:預設初始化的時候就支援兩種媒體型別了,application/json以及 application/*+json ;
這兩種型別是初始化的時候就設定上去的,可以看到下面兩種MediaType :application/json 以及 application/*+json
四.1.7 isCompatibleWith相容性判斷,程式碼片段位於: org.springframework.util.MimeType#isCompatibleWith
MediaType是MimeType的子類,比如MediaType為 application/json型別的在MediaType中,application就是 type,而json就是 subType; 判斷相容性邏輯呢:this是請求中的Accpet,代表客戶端接受的請求媒體型別, other此時代表當前可以返回給你的媒體型別;this 物件或者 other物件的 type一方為 *,那兩個媒體型別就是相容的,比如 */subType1 就是相容任何媒體型別 ; type相等的情況 , subType不存在 +號的情況下 ,有一方子型別為 * ,兩個MediaType也是相容的;兩個MediaType subType一致那更不用說了是相容的,兩個媒體子型別不一致 如 application/json和 application/xml就是不相容的 ; 子型別存在 + 號的情況下,比如application/json和 application/*+json也是不相容的.
public boolean isCompatibleWith(MimeType other) { if (other == null) return false; if (isWildcardType() || other.isWildcardType()) { return true; } else if (getType().equals(other.getType())) { if (getSubtype().equals(other.getSubtype())) return true; // wildcard with suffix? e.g. application/*+xml if (this.isWildcardSubtype() || other.isWildcardSubtype()) { int thisPlusIdx = getSubtype().indexOf('+'); int otherPlusIdx = other.getSubtype().indexOf('+'); if (thisPlusIdx == -1 && otherPlusIdx == -1) { return true; } else if (thisPlusIdx != -1 && otherPlusIdx != -1) { String thisSubtypeNoSuffix = getSubtype().substring(0, thisPlusIdx); String otherSubtypeNoSuffix = other.getSubtype().substring(0, otherPlusIdx); String thisSubtypeSuffix = getSubtype().substring(thisPlusIdx + 1); String otherSubtypeSuffix = other.getSubtype().substring(otherPlusIdx + 1); if (thisSubtypeSuffix.equals(otherSubtypeSuffix) && (WILDCARD_TYPE.equals(thisSubtypeNoSuffix) || WILDCARD_TYPE.equals(otherSubtypeNoSuffix))) { return true; } } } } return false; }
四.1.8 程式碼片段位於: org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters
指定了MediaType為application/json, 遍歷messageConverters集合,分別呼叫canWrite方法判斷是否支援將返回值寫回Response,這次與四.1.5不同的是,指定了MediaType;
this指代RequestResponseBodyMethodProcessor物件,其advice屬性為RequestResponseBodyAdviceChain,beforeBodyWrite方法用來處理@JsonView.
addContentDispositionHeader用來滿足一定條件時設定Content-Disposition響應頭.
準備工作都完成後,呼叫GenericHttpMessageConverter的write方法,這裡完成JSON轉換以及寫到response、設定響應頭的工作.
四.1.9 程式碼片段位於: org.springframework.http.converter.AbstractGenericHttpMessageConverter#write
這裡並沒有開始寫回操作,響應頭也沒有寫回,只是記錄到HttpOutputMessage的HttpHeaders屬性中.
程式碼片段位於: org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#writeInternal
呼叫的是Jackson2的API, 其中 JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);HttpOutputMessage.getBody方法,給response添加了響應頭,並且獲取了response的outputStream流, Jackson2轉換的物件就在這裡直接寫到了響應輸出流中;方法結束之後,直接呼叫response的flush,到這裡@ResponseBody流程大致跑了一遍.
四.2 請求json或者xml形式資料兩種方式 (1)字尾名限制 (2)請求頭限制
前面四.1.5提到支援請求字尾形式解析MediaType,<mvc:annotation-driven/> (spring4.x是這樣) 默默為我們註冊了兩個PathExtensionContentNegotiationStrategy 、HeaderContentNegotiationStrategy. 這兩個ContentNegotiationStrategy的實現類. 先看下這兩個ContentNegotiationStrategy達到了什麼樣的效果?
效果圖如下: SpringMvc攔截 / 的所有請求, 但是可以根據 字尾名 .json .xml來返回對應的結果, 這就是ContentNegotiationStrategy起到的作用;
首先要想實現這種效果,需要的條件有: 1.開啟<mvc:annotation-driven />
2. 引入第三方json的jar, jackson 或者 gson就可以支援 json ,且不需要自己配置HttpMessageConverter,除此之外的 json 第三方jar需要自己配置 HttpMessageConverter;
3.不引入第三方xml的jar情況下,jdk自帶的jaxb,實體類上標註@XmlRootElement即可支援xml ;pom依賴引入 jackson-dataformat-xml 即可使用jackson,將實體類轉為xml;
這種情況使用方式: 1.字尾名請求: url後跟上需要的型別.json 或者 .xml
2.請求頭Accept:application/json 或者 application/xml
悄悄說下我的發現,假如不加字尾名請求, 如果Xml、Json都支援,那會先返回Xml結果
四.2.1 HeaderContentNegotiationStrategy原理記錄
程式碼片段位置: org.springframework.web.accept.HeaderContentNegotiationStrategy#resolveMediaTypes
根據request物件的Accept請求頭字串,轉換為MediaType集合,也就是四.1.5的邏輯
四.2.2 ServletPathExtensionContentNegotiationStrategy原理記錄
程式碼片段位於: org.springframework.web.accept.PathExtensionContentNegotiationStrategy#getMediaTypeKey
ServletPathExtensionContentNegotiationStrategy 父類AbstractMappingContentNegotiationStrategy實現resolveMediaTypes方法,resolveMediaTypes呼叫resolveMediaTypeKey方法,resolveMediaTypeKey呼叫getMediaTypeKey方法,其實就是解析了請求的字尾名,比如 json或者xml, 根據字尾名去mediaTypes集合查詢對應的MediaType,ServletPathExtensionContentNegotiationStrategy的mediaTypes是解析<mvc:annotation-driven/>時候動態判斷jar(jackson gson jaxb這些jar)包新增的,暫時只支援到json / xml這兩個,不過也是支援擴充套件的;
五. Spring偏底層記錄.
五.1 MappingJackson2HttpMessageConverter是如何建立,又如何加入到上面四.1的HandlerMethodReturnValueHandlerComposite中?
程式碼片段位於: org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet
HandlerMethodReturnValueHandlerComposite是RequestMappingHandlerAdapter的屬性,其afterPropertiesSet方法最後, getDefaultReturnValueHandlers方法獲取到的HandlerMethodReturnValueHandler集合,加入到了returnValueHandlers中; getDefaultReturnValueHandlers 有這樣一句程式碼:
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));
這裡可以看到RequestResponseBodyMethodProcessor的三個重要屬性都已經賦值完畢,messageConverters、contentNegotiationManager、requestResponseBodyAdvice,而且都是直接引用的RequestMappingHandlerAdapter物件的屬性. 問題就歸結於RequestMappingHandlerAdapter的三個屬性了.
程式碼片段位於: org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDefaultReturnValueHandlers
五.1.1 RequestMappingHandlerAdapter的messageConverters哪裡來的?
程式碼片段位於: org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser#getMessageConverters
<mvc:annotation-driven/>開啟之後, AnnotationDrivenBeanDefinitionParser的parse方法進行解析; 解析規則:mvc:annotation-driven下有message-converters子標籤,就將這個messageConverter物件加入messageConverters集合,同時,如果message-converters這個子標籤的register-defaults屬性為true,那把Spring為我們預設建立的一起加入到messageConverters,該register-defaults屬性預設為true; 這就意味著 上面我的那種寫法其實是兩個MappingJackson2HttpMessageConverter,但是自定義加入的HttpMessageConverter會在集合的前端.
沒有message-converters子標籤,那就直接使用Spring為我們預設建立的HttpMessageConverter物件,常見的ByteArrayHttpMessageConverter 這些都是Spring預設會新增的,下圖就是幾個比較複雜的, 比如當前引入了jackson-dataformat-xml這個包,那jackson2XmlPresent就為true,那預設就不會註冊jaxb的JaxbRootElementHttpMessageConverter;
比如引入了jackson-databind以及相關jar,那註冊的就是MappingJackson2HttpMessageConverter,而不會註冊Gson相對應的HttpMessageConverter了;
這裡也就看到了MappingJackson2HttpMessageConverter如何建立的,原來是我們引入了jackson-databind相關的jar包,<mvc:annotation-driven/>就會自動建立;
程式碼片段位於: org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#RequestResponseBodyMethodProcessor
結合五.1來看,RequestResponseBodyMethodProcessor,驗證了四.1.6,RequestResponseBodyMethodProcessor的allSupportedMediaTypes是遍歷的messageConverters的getSupportedMediaTypes來的.
五.1.2 requestResponseBodyAdvice作用簡單記錄下.
requestResponseBodyAdvice 是Spring幫我們建立的,目前是為了支援@JsonView註解,定義上說是為了在@ResponseBody處理之前進行一些切面操作.
五.1.3 contentNegotiationManager
contentNegotiationManager我想我們應該主要關心,什麼時候註冊的兩個四.2的HeaderContentNegotiationStrategy以及ServletPathExtensionContentNegotiationStrategy?
程式碼片段位於: org.springframework.web.accept.ContentNegotiationManagerFactoryBean#afterPropertiesSet
ContentNegotiationManager是通過FactoryBean ContentNegotiationManagerFactoryBean來實現生成, 在這裡就可以發現設定的兩種ContentNegotiationStrategy.