使用过springmvc都知道,我们只需编写Handler的逻辑和返回结果,而返回的结果可以是任意类型的,springmvc是如何处理这些类型的呢。
一 首先了解两个概念:
1 返回值处理器(HandlerMethodReturnValueHandler):对于请求访问,不同的请求,需要不用的Handler,同样的道理,对于Handler的不同返回值,也需要不同的返回值处理器(HandlerMethodReturnValueHandler)来处理。HandlerMethodReturnValueHandler集合配置在适配器的List<HandlerMethodReturnValueHandler> customReturnValueHandlers属性里面。
默认的返回值处理器有:
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList();
handlers.add(new ModelAndViewMethodReturnValueHandler());
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new ResponseBodyEmitterReturnValueHandler(this.getMessageConverters(), this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
handlers.add(new StreamingResponseBodyReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));
handlers.add(new HttpHeadersReturnValueHandler());
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
handlers.add(new ModelAttributeMethodProcessor(false));
handlers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
if (this.getCustomReturnValueHandlers() != null) {
handlers.addAll(this.getCustomReturnValueHandlers());
}
if (!CollectionUtils.isEmpty(this.getModelAndViewResolvers())) {
handlers.add(new ModelAndViewResolverMethodReturnValueHandler(this.getModelAndViewResolvers()));
} else {
handlers.add(new ModelAttributeMethodProcessor(true));
}
return handlers;
}
常用的有:
- ModelAndViewMethodReturnValueHandler:支持返回值是ModelAndView类型的。
- ModelMethodProcessor:支持返回值是Model的。
- ViewMethodReturnValueHandler:支持返回值是View的。
- HttpEntityMethodProcessor:支持返回值是HttpEntity的。
- RequestResponseBodyMethodProcess:支持类上或者方法上含有@ResponseBody注解的(这里主要讲解这个返回值处理器)。
- ViewNameMethodReturnValueHandler:支持返回类型是void或者String。
获取返回值处理器的规则是从上到下返回第一个匹配的处理器。
2 返回值转换器(HttpMessageConverter):当找到合适的返回值处理器(HandlerMethodReturnValueHandler)后,处理器还需要调用对应的返回值转换器来对返回值进行转换,然后才输出到响应体里面。需要使用默认转换器的主要是ResponseBodyEmitterReturnValueHandler、HttpEntityMethodProcessor、RequestResponseBodyMethodProcessor这三个返回值处理器。
特别提示:返回值转换器配置在适配器RequestMappingHandlerAdapter的private List<HttpMessageConverter<?>> messageConverters属性里面。
默认的返回值转换器有:
public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter
= new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false);
this.messageConverters = new ArrayList(4);
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
try {
this.messageConverters.add(new SourceHttpMessageConverter());
} catch (Error var3) {
}
this.messageConverters
.add(new AllEncompassingFormHttpMessageConverter());
}
转换器转换规则--遍历返回值转换器集合,使用返回值转换器的canWrite(Class<?> clazz, @Nullable MediaType mediaType)方法判断是否匹配,在里面主要会进行类型是否支持和媒体是否支持的判断,两者同时满足表示匹配成功,使用该转换器处理返回值,canWrite(Class<?> clazz, @Nullable MediaType mediaType)如下所示。
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
//this.supports(clazz) 判断类型是否匹配,
//this.canWrite(mediaType)判断媒体是否匹配
return this.supports(clazz) && this.canWrite(mediaType);
}
常用类型转换器匹配规则:
- ByteArrayHttpMessageConverter:匹配 byte[]数组,默认匹配媒体类型有 [application/octet-stream, */*]
- StringHttpMessageConverter:匹配String类型,默认匹配媒体类型集合有 [text/plain, */*]
- SourceHttpMessageConverter:匹配DOMSource、SAXSource、StAXSource、StreamSource、Source类型,默认匹配媒体类型集合有[application/xml, text/xml, application/*+xml]
- AllEncompassingFormHttpMessageConverter:匹配MultiValueMap类型或是其子类,匹配媒体类型集合有[application/x-www-form-urlencoded, multipart/form-data]
注意:媒体mediaType有两个属性,分别是type和subtype,比如媒体类型application/octet-stream的type=application、subtype=octet-stream。两个媒体的type和subtype属性相等即匹配成功。另外,*/*表示通配任意type与subtype的值,所以默认匹配媒体类型如果有*/* ,那么媒体类型肯定可以匹配成功,我们只需关注this.supports(clazz)即类型判断就行了。还有就是,媒体类型首先是从response对象里面获取content-type的header值,如果没有获取到,即响应没有设置content-type的header值,springmvc会自己尝试给定一个可接受的媒体值用于媒体判断。当然,如果不想使用默认媒体类型,还可以自定义媒体类型,只需在自定义返回值转换器的时候指定supportedMediaTypes即可。
注意:返回值转换器默认的编码是ISO-8859-1,在将返回值写入响应体里面时会使用该编码方式编码后再写入所以会出现乱码问题。什么意思,意思就是假如你的返回值是utf-8编码,然后也告诉了浏览器返回值的编码方式是utf-8(通过response对象告诉浏览器),最后发现显示时任然是乱码,就是因为返回值转换器在将返回值写入响应体之前还要对其进行一次编码,而默认使用的是ISO-8859-1编码方式,导致乱码问题。
配置返回值转换器的媒体类型与编码方式(utf-8):
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.StringHttpMessageConverter" >
<property name="defaultCharset" value="utf-8"></property>
<property name="supportedMediaTypes">
<list>
<value>text/html</value>
<value>*/*</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
二 @ResponseBody字符串中文乱码问题解决
由以上知道,使用了@ResponseBody的handler使用的是RequestResponseBodyMethodProcess返回值处理器,该处理器需要使用默认返回值转换器集合中的转换器来处理返回值。
知道了返回值的处理规则后,这里来说一下如何处理返回字符串中文乱码问题。首先我们要知道,出现乱码就是因为从返回值到浏览器显示时编码方式不一致导致的,就是因为转换器的默认编码方式为iso-8859-1。知道原因过后修改转换器的编码方式就行了。
字符串返回值使用的是StringHttpMessageConverter转换器,并且我们知道转换器集合配置在适配器的private List<HttpMessageConverter<?>> messageConverters属性里面,解决办法就是自己配置messageConverters里面的转换器,并为转换器的defaultCharset属性配置编码为utf-8,需要注意的是自己配置后以前默认的转换器就不存在了:
<!--配置方式一-->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="utf-8"></property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!--配置方式二-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.StringHttpMessageConverter" >
<property name="defaultCharset" value="utf-8"></property>
</bean>
</list>
</property>
</bean>
两种配置是根据引入适配器与映射器方式的不同而不同,但是原理都一样,都是配置适配器的messageConverters属性。
三 @ResponseBody返回json字符串
我们知道,springmvc默认的返回值转换器只能匹配有限类型,其中就不包括我们定义的一些实体类,如果返回值是一些实体类型,会抛出没有对应的返回值转换器异常。
org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class com.dahuici.zyb.entity.User
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:233) org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:180)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:119)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
springmvc为我们准备了很多转换器,如下图所示
这里我们使用的是json里面的MappingJackson2HttpMessageConverter
MappingJackson2HttpMessageConverter 默认匹配的媒体类型有 [application/json, application/*+json],至于对于返回值类型来说,唯一的要求好像就是需要有set与get方法,当然,如果是String类型不需要set与get(这一段的类型判断超出技术水平了)。
另外,MappingJackson2HttpMessageConverter使用到了其他的包(jackson-databind,jackson-core最少要导入这两个),所以需要导入如下依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.5</version>
</dependency>
然后将其配置给适配器的messageConverters属性即可,需要注意的是也要配置编码方式为utf-8,不然返回的json字符串会出现乱码。
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" >
<property name="defaultCharset" value="utf-8"></property>
</bean>
</list>
</property>
</bean>