zoukankan      html  css  js  c++  java
  • 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仍在使用,访问到飞起,安装方式自行百度

  • 相关阅读:
    zookeeper报错java.net.ConnectException: Connection refused: no further information
    dubbo 使用zookeeper 出现 Dubbo客户端调用报错NullPointerException
    docker下载镜像received unexpected Http status:500 Internal Server Error
    Spring Security 无法登陆,报错:There is no PasswordEncoder mapped for the id “null”
    js 解决中文乱码的问题
    使用Elasticsearch 出现的拒绝连接
    com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class cn.edu.
    yii2判断数据库字段is null
    MySQL之终端(Terminal)管理数据库、数据表、数据的基本操作
    yii2.0-rules验证规则应用实例
  • 原文地址:https://www.cnblogs.com/lvbinbin2yujie/p/10513530.html
Copyright © 2011-2022 走看看