SpringMvc @ResponseBody


 

 

一. @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時間類型是返回時間戳類型,至於其他版本測試就可以知道是否直接返回時間戳類型;

  image

二.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的!

image_thumb31[1]

  說明:方式三應該是日常開發中最方便的,只需要在實體類上添加@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.

image

 

四.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類型.

image

 

代碼片段位於org.springframework.web.accept.ContentNegotiationManager#resolveMediaTypes

this對象指代ContentNegotiationManager,遍歷其ContentNegotiationStrategy集合strategies, 調用接口的resolveMediaTypes來解析MediaType.  如果需要自定義解析請求策略,可以實現該接口ContentNegotiationStrategy.  <mvc:annotation-driven/> (spring4.x是這樣) 默默為我們注冊了兩個PathExtensionContentNegotiationStrategy 、HeaderContentNegotiationStrategy,作用分別是用來解析 請求后綴形式、  Http  Accept的請求頭.

image

 

四.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的集合,代表可以支持的響應媒體類型 ;

image

 

當前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處理;

image

 

代碼片段位於:org.springframework.http.converter.AbstractHttpMessageConverter#canWrite(java.lang.Class<?>, org.springframework.http.MediaType)

image

 

代碼片段位於:org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#canWrite

image_thumb14[1]

 

代碼片段位於:org.springframework.http.converter.AbstractHttpMessageConverter#getSupportedMediaTypes

MappingJackson2HttpMessageConverter的getSupportedMediaTypes:默認初始化的時候就支持兩種媒體類型了,application/json以及 application/*+json ;

image_thumb18[1]

這兩種類型是初始化的時候就設置上去的,可以看到下面兩種MediaType :application/json  以及 application/*+json

image_thumb20[1]

 

四.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、設置響應頭的工作.

image

 

四.1.9 代碼片段位於:org.springframework.http.converter.AbstractGenericHttpMessageConverter#write

這里並沒有開始寫回操作,響應頭也沒有寫回,只是記錄到HttpOutputMessage的HttpHeaders屬性中.

image_thumb24[1]

 

代碼片段位於: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流程大致跑了一遍.

image

 

四.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結果

image

 

四.2.1 HeaderContentNegotiationStrategy原理記錄

代碼片段位置:org.springframework.web.accept.HeaderContentNegotiationStrategy#resolveMediaTypes

根據request對象的Accept請求頭字符串,轉換為MediaType集合,也就是四.1.5的邏輯

image_thumb15[1]

 

四.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這兩個,不過也是支持擴展的;

image_thumb17[1]

 


五. 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的三個屬性了.

image_thumb34[1]

 

代碼片段位於:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDefaultReturnValueHandlers

image

 

五.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/>就會自動創建;

image_thumb40[1]

 

代碼片段位於:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#RequestResponseBodyMethodProcessor

結合五.1來看,RequestResponseBodyMethodProcessor,驗證了四.1.6,RequestResponseBodyMethodProcessor的allSupportedMediaTypes是遍歷的messageConverters的getSupportedMediaTypes來的.

 

image_thumb2[2]

 

五.1.2 requestResponseBodyAdvice作用簡單記錄下.

requestResponseBodyAdvice 是Spring幫我們創建的,目前是為了支持@JsonView注解,定義上說是為了在@ResponseBody處理之前進行一些切面操作.

 

五.1.3 contentNegotiationManager

contentNegotiationManager我想我們應該主要關心,什么時候注冊的兩個四.2的HeaderContentNegotiationStrategy以及ServletPathExtensionContentNegotiationStrategy?

代碼片段位於:org.springframework.web.accept.ContentNegotiationManagerFactoryBean#afterPropertiesSet

ContentNegotiationManager是通過FactoryBean ContentNegotiationManagerFactoryBean來實現生成, 在這里就可以發現設置的兩種ContentNegotiationStrategy.

image_thumb61

 


六.參考文章

六.1 推薦一個非常有用的網站,學習Jackson很有幫助的網站

https://www.baeldung.com/category/json/jackson/

 

六.2 推薦一個Firefox上訪問StackOverFlow起飛的插件

網站訪問StackOverFlow,由於谷歌被牆,  安裝插件Decentraleyes,2019.3.13仍在使用,訪問到飛起,安裝方式自行百度


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM