Spring MVC处理响应的 header
我们经常需要在HttpResponse中设置一些headers,我们使用Spring MVC框架的时候我们如何给Response设置Header呢?
So easy, 看下面的代码:
@RequestMapping(value = "/rulelist", method = RequestMethod.GET) @ResponseBody
public String getRuleList(HttpServletRequest request,
HttpServletResponse response) {
response.addHeader("test", "test");
return service.getRuleList();
}
通过验证,我们可以看到test项已经被成功添加到response的头部信息
Content-Length: 2 kilobytes
Content-Type: text/plain;charset=ISO-8859-1
Server: Apache-Coyote/1.1
test: test
接下来,我们希望修改Content-Type,从而统一服务器端和客户端的内容编码。我们继续修改代码,
@RequestMapping(value = "/rulelist", method = RequestMethod.GET)
@ResponseBody
public String getRuleList(HttpServletRequest request,
HttpServletResponse response) {
response.addHeader("Content-Type", "application/json;charset=UTF-8");
return service.getRuleList();
}
接下来,我们验证一下结果:
Content-Length: 2 kilobytes
Content-Type: text/plain;charset=ISO-8859-1
Server: Apache-Coyote/1.1
和我们预想的并一样,response的content-type header没有被设置成"application/json;charset=UTF-8",很令人困惑。
那么,接下来让我们来探索下Spring MVC内部是如何处理这一过程的。首先我们先要对Spring MVC框架处理Http请求的流程有一个整体的了解。
下图清晰地向大家展示了Spring MVC处理HTTP请求的流程,(图片来自网络)

具体流程如下:
1. DispatcherServlet接收到Request请求
2. HandlerMapping选择一个合适的Handler处理Request请求
3-4. 选择合适的HandlerAdapter,调用用户编写的Controller处理业务逻辑。(HandlerAdapter主要是帮助Spring MVC支持多种类型的Controller)
5. Controller将返回结果放置到Model中并且返回view名称给Handler Adapter
6. DispatcherServlet选择合适的ViewResolver来生成View对象
7-8. View对象利用Model中的数据进行渲染并返回数据
相信大家对于上面的处理流程并不陌生,上面的流程图向我们展示了SpringMVC生成ModelAndView并返回response的大体流程。
下面我们来看看我们上面代码片段的处理流程是如何进行的?

从上面的流程图我们可以看到,content-type header是单独被处理的,具体过程可以参考下面的源码(AbstractMessageConverterMethodProcessor):
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException { Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
HttpServletRequest servletRequest = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest); //适合的兼容media types类型实际上,我们可以使用produces = {}来指定我们需要的mediatype
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass); Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (compatibleMediaTypes.isEmpty()) {
if (returnValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
} List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes); MediaType selectedMediaType = null; //选择最匹配的mediaType
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;
}
} if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) { //遍历messageConvertors, 寻找可以处理相应返回类型和mediatype的HttpMessageConvertor
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
(Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
if (returnValue != null) { //这里将会填充mediatype到header,并将httpmessage发送给请求者
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
messageConverter + "]");
}
}
return;
}
}
} if (returnValue != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
接下来,将选择好的mediatype写入到HttpOutputMessage中
public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException { final HttpHeaders headers = outputMessage.getHeaders(); //设置contenttype到HttpOutputMessage
if (headers.getContentType() == null) {
MediaType contentTypeToUse = contentType;
if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {
contentTypeToUse = getDefaultContentType(t);
}
if (contentTypeToUse != null) {
headers.setContentType(contentTypeToUse);
}
}
if (headers.getContentLength() == -1) {
Long contentLength = getContentLength(t, headers.getContentType());
if (contentLength != null) {
headers.setContentLength(contentLength);
}
}
/* 省略了不相干代码 */
}
最终的Headers设置在ServletServerHttpResponse类中完成,
private void writeHeaders() {
if (!this.headersWritten) {
for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
String headerName = entry.getKey();
for (String headerValue : entry.getValue()) { //将复合类中之前设置的header(content-type)内容补充到servletResponse
this.servletResponse.addHeader(headerName, headerValue);
}
}
// HttpServletResponse exposes some headers as properties: we should include those if not already present
if (this.servletResponse.getContentType() == null && this.headers.getContentType() != null) {
this.servletResponse.setContentType(this.headers.getContentType().toString());
}
if (this.servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null &&
this.headers.getContentType().getCharSet() != null) {
this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharSet().name());
}
this.headersWritten = true;
}
}
从上述的代码中,我们可以看到在RequestResponseBodyMethodProcessor这个ReturnValueHandler中,media-type被单独的逻辑进行处理,因此直接在ServletResponse中设置content-type header并不能正常生效。
需要在@RequestMapping中添加produces = {} 进行设置才可以。
参考:https://www.cnblogs.com/kaiblog/p/7565231.html
我们经常需要在HttpResponse中设置一些headers,我们使用Spring MVC框架的时候我们如何给Response设置Header呢?
Sooooooooooooo easy, 看下面的代码:
|
1
2
3
4
5
6
7
|
@RequestMapping(value = "/rulelist", method = RequestMethod.GET)@ResponseBodypublic String getRuleList(HttpServletRequest request, HttpServletResponse response) { response.addHeader("test", "test"); return service.getRuleList();} |
通过验证,我们可以看到test项已经被成功添加到response的头部信息
|
1
2
3
4
|
Content-Length: 2 kilobytesContent-Type: text/plain;charset=ISO-8859-1Server: Apache-Coyote/1.1test: test |
接下来,我们希望修改Content-Type,从而统一服务器端和客户端的内容编码。我们继续修改代码,
|
1
2
3
4
5
6
7
|
@RequestMapping(value = "/rulelist", method = RequestMethod.GET)@ResponseBodypublic String getRuleList(HttpServletRequest request, HttpServletResponse response) { response.addHeader("Content-Type", "application/json;charset=UTF-8"); return service.getRuleList();} |
接下来,我们验证一下结果:
|
1
2
3
|
Content-Length: 2 kilobytesContent-Type: text/plain;charset=ISO-8859-1Server: Apache-Coyote/1.1 |
和我们预想的并一样,response的content-type header没有被设置成"application/json;charset=UTF-8",很令人困惑。
那么,接下来让我们来探索下Spring MVC内部是如何处理这一过程的。首先我们先要对Spring MVC框架处理Http请求的流程有一个整体的了解。
下图清晰地向大家展示了Spring MVC处理HTTP请求的流程,(图片来自网络)

具体流程如下:
1. DispatcherServlet接收到Request请求
2. HandlerMapping选择一个合适的Handler处理Request请求
3-4. 选择合适的HandlerAdapter,调用用户编写的Controller处理业务逻辑。(HandlerAdapter主要是帮助Spring MVC支持多种类型的Controller)
5. Controller将返回结果放置到Model中并且返回view名称给Handler Adapter
6. DispatcherServlet选择合适的ViewResolver来生成View对象
7-8. View对象利用Model中的数据进行渲染并返回数据
相信大家对于上面的处理流程并不陌生,上面的流程图向我们展示了SpringMVC生成ModelAndView并返回response的大体流程。
下面我们来看看我们上面代码片段的处理流程是如何进行的?

从上面的流程图我们可以看到,content-type header是单独被处理的,具体过程可以参考下面的源码(AbstractMessageConverterMethodProcessor):
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException { Class<?> returnValueClass = getReturnValueType(returnValue, returnType); HttpServletRequest servletRequest = inputMessage.getServletRequest(); List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest); //适合的兼容media types类型实际上,我们可以使用produces = {}来指定我们需要的mediatype List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass); Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>(); for (MediaType requestedType : requestedMediaTypes) { for (MediaType producibleType : producibleMediaTypes) { if (requestedType.isCompatibleWith(producibleType)) { compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType)); } } } if (compatibleMediaTypes.isEmpty()) { if (returnValue != null) { throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes); } return; } List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes); MediaType.sortBySpecificityAndQuality(mediaTypes); MediaType selectedMediaType = null; //选择最匹配的mediaType 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; } } if (selectedMediaType != null) { selectedMediaType = selectedMediaType.removeQualityValue(); for (HttpMessageConverter<?> messageConverter : this.messageConverters) { //遍历messageConvertors, 寻找可以处理相应返回类型和mediatype的HttpMessageConvertor if (messageConverter.canWrite(returnValueClass, selectedMediaType)) { returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType, (Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage); if (returnValue != null) { //这里将会填充mediatype到header,并将httpmessage发送给请求者 ((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage); if (logger.isDebugEnabled()) { logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" + messageConverter + "]"); } } return; } } } if (returnValue != null) { throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes); }} |
接下来,将选择好的mediatype写入到HttpOutputMessage中
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { final HttpHeaders headers = outputMessage.getHeaders(); //设置contenttype到HttpOutputMessage if (headers.getContentType() == null) { MediaType contentTypeToUse = contentType; if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) { contentTypeToUse = getDefaultContentType(t); } if (contentTypeToUse != null) { headers.setContentType(contentTypeToUse); } } if (headers.getContentLength() == -1) { Long contentLength = getContentLength(t, headers.getContentType()); if (contentLength != null) { headers.setContentLength(contentLength); } } /* 省略了不相干代码 */} |
最终的Headers设置在ServletServerHttpResponse类中完成,
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
private void writeHeaders() { if (!this.headersWritten) { for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) { String headerName = entry.getKey(); for (String headerValue : entry.getValue()) { //将复合类中之前设置的header(content-type)内容补充到servletResponse this.servletResponse.addHeader(headerName, headerValue); } } // HttpServletResponse exposes some headers as properties: we should include those if not already present if (this.servletResponse.getContentType() == null && this.headers.getContentType() != null) { this.servletResponse.setContentType(this.headers.getContentType().toString()); } if (this.servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null && this.headers.getContentType().getCharSet() != null) { this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharSet().name()); } this.headersWritten = true; }} |
从上述的代码中,我们可以看到在RequestResponseBodyMethodProcessor这个ReturnValueHandler中,media-type被单独的逻辑进行处理,因此直接在ServletResponse中设置content-type header并不能正常生效。
需要在@RequestMapping中添加produces = {} 进行设置才可以。
Spring MVC处理响应的 header的更多相关文章
- Spring MVC能响应HTTP请求的原因?
很多Java面试官喜欢问这个问题: 一个Spring MVC的项目文件里,开发人员没有开发自己的Servlet,只通过注解@RequestMapping定义了方法home能响应发向 /mvc/test ...
- Spring MVC @ResponseBody响应中文乱码
问题:在前端通过get请求服务端返回String类型的服务时,会出现中文乱码问题 原因:由于spring默认对String类型的返回的编码采用的是 StringHttpMessageConverter ...
- Spring MVC执行的流程
1.Spring MVC应用的开发步骤 a.在web.xml文件中定义前端控制器DispatcherServlet来拦截用户请求.由于Web应用是基于请求/响应架构的应用,所以 不管哪个MVC Web ...
- Spring MVC 简述:从MVC框架普遍关注的问题说起
任何一个完备的MVC框架都需要解决Web开发过程中的一些共性的问题,比如请求的收集与分发.数据前后台流转与转换,当前最流行的SpringMVC和Struts2也不例外.本文首先概述MVC模式的分层思想 ...
- Spring MVC详细讲解
一:三层架构和MVC 1:三层架构 我们的开发架构一般都是基于两种形式:一种是 C/S 架构,也就是客户端/服务器,另一种是 B/S 架构,也就是浏览器服务器.在 JavaEE 开发中,几乎全都是基于 ...
- 前端控制器是整个MVC框架中最为核心的一块,它主要用来拦截符合要求的外部请求,并把请求分发到不同的控制器去处理,根据控制器处理后的结果,生成相应的响应发送到客户端。前端控制器既可以使用Filter实现(Struts2采用这种方式),也可以使用Servlet来实现(spring MVC框架)。
本文转自http://www.cnblogs.com/davidwang456/p/4090058.html 感谢作者 前端控制器是整个MVC框架中最为核心的一块,它主要用来拦截符合要求的外部请求,并 ...
- 从content-type设置看Spring MVC处理header的一个坑
我们经常需要在HttpResponse中设置一些headers,我们使用Spring MVC框架的时候我们如何给Response设置Header呢? Sooooooooooooo easy, 看下面的 ...
- spring mvc 通过拦截器记录请求数据和响应数据
spring mvc 能过拦截器记录请求数据记录有很多种方式,主要有以下三种: 1:过滤器 2:HandlerInterceptor拦截器 3:Aspect接口控制器 但是就我个人所知要记录返回的数据 ...
- spring MVC 返回值信息和ResponseBody的响应json数据
spring mvc的界面返回: 如果我们定义的返回类型是String 那么我们返回的时候直接写入 我们的界面的名字就可以了 springmvc会自动去找到我们的界面,如果是void类型的返回那么 ...
随机推荐
- SQL语句中 chinese_prc_CS_AI_WS 以及replace用法
Select * from [DBData].[dbo].[T_Student] where Name='lilei' 查询结果如下: 结论:由查询结果可知 SQL Server ...
- typedef struct bit0 : 1
这句话定义了一个位域,bit0是该位域的域名,而且bit0只占用一个位.位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位.为了节省存储空间,并使处理简便,C语言提供了一种 ...
- Windows下编译TensorFlow1.3 C++ library及创建一个简单的TensorFlow C++程序
由于最近比较忙,一直到假期才有空,因此将自己学到的知识进行分享.如果有不对的地方,请指出,谢谢!目前深度学习越来越火,学习.使用tensorflow的相关工作者也越来越多.最近在研究tensorflo ...
- 个人作业-Week 2
一.代码复审 概要部分 代码能符合需求和规格说明么? 能: 代码设计是否有周全的考虑? 有较为周全的考虑: 代码可读性如何? 可读性一般: 代码容易维护么? 不太容易维护: 代码的每一行都执行并检查过 ...
- [日常工作] 应用服务器上面应该尽量少开各种应用 --Chrome 内存泄露 让应用服务器非常缓慢
1. 前段时间修改 服务器的密码 导致应用程序的web site 启动有问题 ,打开chrome 查看了错误详细信息 但是忘记关了.. 今天反馈机器非常缓慢 简单看了下内存 吐血... 所以以后不能在 ...
- poj 3254(状态压缩DP)
poj 3254(状态压缩DP) 题意:一个矩阵里有很多格子,每个格子有两种状态,可以放牧和不可以放牧,可以放牧用1表示,否则用0表示,在这块牧场放牛,要求两个相邻的方格不能同时放牛,即牛与牛不能相 ...
- Python学习笔记day01--Python基础
1 python的应用 Python崇尚优美.清晰.简单,是一个优秀并广泛使用的语言. Python可以应用于众多领域,如:数据分析.组件集成.网络服务.图像处理.数值计算和科学计算等 ...
- mybatis的setting
在mybaits中,setting的的配置参数如下(如果不在配置文件中配置将使用默认值): 设置参数 描述 有效值 默认值 cacheEnabled 该配置影响的所有映射器中配置的缓存的全局开关 tr ...
- 【刷题】洛谷 P4319 变化的道路
题目描述 小 w 和小 c 在 H 国,近年来,随着 H 国的发展,H 国的道路也在不断变化着 根据 H 国的道路法,H 国道路都有一个值 \(w\) ,表示如果小 w 和小 c 通过这条道路,那么他 ...
- Nagios通过企业微信报警
主要分两部分进行: 注册企业微信,自建应用,获取与发送消息相关的信息: 编写调用微信API脚本(bash),配置Nagios微信报警: 一.企业微信 1.注册企业微信:https://work.wei ...