一. @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.


六.参考文章

六.1 推荐一个非常有用的网站,学习Jackson很有帮助的网站

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

六.2 推荐一个Firefox上访问StackOverFlow起飞的插件

网站访问StackOverFlow,由于谷歌被墙,  安装插件Decentraleyes,2019.3.13仍在使用,访问到飞起,安装方式自行百度

SpringMvc @ResponseBody的更多相关文章

  1. SpringMVC @ResponseBody 415错误处理

    在查看下面部分内容之前,请先检查你的请求蚕食是否正确,如果全部正确,请继续往下看 刚开始用SpringMVC, 页面要使用jQuery的ajax请求Controller. 但总是失败,主要表现为以下两 ...

  2. SpringMVC @ResponseBody的使用

    原文链接:http://www.jianshu.com/p/7097fea8ce3f@ResponseBody用法作用:该注解用于将Controller的方法返回的对象,根据HTTP Request ...

  3. 解决spring-mvc @responseBody注解返回json 乱码问题

    在使用spring-mvc的mvc的时候既享受它带来的便捷,又头痛它的一些问题,比如经典的中文乱码问题.现在是用json作为客户端和服务端 的数据交换格式貌似很流行,但是在springmvc中有时候会 ...

  4. SpringMvc @ResponseBody字符串中文乱码原因及解决方案

    今天突然发现一个问题,后来在网上也找到了很多解决思路,自己也查找到了问题所在,记录一下. @RequestMapping(value = "/demo1") @ResponseBo ...

  5. SpringMVC @ResponseBody返回中文乱码

    SpringMVC的@ResponseBody返回中文乱码的原因是SpringMVC默认处理的字符集是ISO-8859-1, 在Spring的org.springframework.http.conv ...

  6. springmvc @responsebody 406/415问题解决

    提供几个解决思路 1.如果项目中用的spring jar包是4.x版本, 需要jackson-annotations-2.x/jackson-core-2.x/jackson-databind-2.x ...

  7. SpringMVC ResponseBody返回中文乱码解决方案

    @RequestMapping(value = "/getForm") @ResponseBody public List<String> getForm(String ...

  8. SpringMVC @ResponseBody 406

    使用@ResponseBody注解可以让Controller返回json格式的数据,在需要传输一个对象信息的时候往往使用这种方式.如果在使用的时候遇到了406,一般原因是: 缺少jar包.转换成jso ...

  9. SpringMVC @ResponseBody和@RequestBody使用

    @ResponseBody用法 作用: 该注解用于将Controller的方法返回的对象,根据HTTP Request Header的Accept的内容,通过适当的HttpMessageConvert ...

随机推荐

  1. SSM框架报错分析(一)——There is no getter for property named 'XXX' in 'class java.lang.String'

    一.发现问题 <select id="queryStudentByNum" resultType="student" parameterType=&quo ...

  2. 基于神经网络的颜色恒常性—Fully Convolutional Color Constancy with Confidence-weighted Pooling

    论文地址: http://openaccess.thecvf.com/content_cvpr_2017/papers/Hu_FC4_Fully_Convolutional_CVPR_2017_pap ...

  3. shiro自定义realm支持MD5算法认证(六)

    1.1     散列算法 通常需要对密码 进行散列,常用的有md5.sha, 对md5密码,如果知道散列后的值可以通过穷举算法,得到md5密码对应的明文. 建议对md5进行散列时加salt(盐),进行 ...

  4. 一套oracle的练习题

    create table student( sno varchar2(10) primary key, sname varchar2(20), sage number(2), ssex varchar ...

  5. Jetson tk1 hash sum mismatch

    sudo apt-get update遭遇Hash Sum Mismatch 修改DNS服务器地址: sudo gedit /etc/resolv.conf 解决办法: 在装有goagent的情况下: ...

  6. Jquery中AJAX参数详细介绍

    Jquery中AJAX参数详细列表: 参数名 类型 描述 url String (默认: 当前页地址) 发送请求的地址. type String (默认: "GET") 请求方式 ...

  7. php- post表单 input name属性的问题

    <input type='text'  style='width: 99px' name='deptNo'></td> name为字符串的时候传递的是单个字符串 <inp ...

  8. ES系列十一、ES的index、store、_source、copy_to和all的区别

    1.基本概念 1.1._source 存储的原始数据._source中的内容就是搜索api返回的内容,如: { "query":{  "term":{   &q ...

  9. kafka系列五、kafka常用java API

    引入maven包 <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka- ...

  10. 在12C上创建wm_concat函数

    11gr2和12C上已经摒弃了wm_concat函数,当时我们很多程序员在程序中确使用了该函数,导致程序出现错误,为了减轻程序员修改程序的工作量,只有通过手工创建个wm_concat函数,来临时解决该 ...