我们经常需要在HttpResponse中设置一些headers,我们使用Spring MVC框架的时候我们如何给Response设置Header呢?

Sooooooooooooo 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 = {} 进行设置才可以。

从content-type设置看Spring MVC处理header的一个坑的更多相关文章

  1. 我看Spring MVC系列(一)

    1.Spring MVC是什么: Spring MVC:Spring框架提供了构建Web应用程序的全功能MVC模块. 2.Spring helloWorld应用(基于Spring 4.2) 1.添加S ...

  2. spring mvc mybatis集成踩的坑

    开园这么多年了也没写几篇文章,现在想想光看别人的也不行啊,咱也自己写写,就写这天我我在做spring mvc与mybatis的集成时遇到的问题 1 spring与mybatis的集成 这个相信大家都弄 ...

  3. spring mvc 重新定向到一个新的Url

    在写项目的时候要求根据请求的参数的不同重新将请求分发,在查阅了spring mvc的一些资料无果后(想使用拦截器去做)就没办法使用重定向的方式去写了 /** * 通过访问API的方式分发请求 * * ...

  4. 基于maven来Spring MVC的环境搭建遇到“坑”

    1.注解配置路径问题: 在web.xml中配置spring mvc 路径时, 应该配置如下:classpath:classpath:spring-* 2.jdk版本和Spring MVC版本不一致问题 ...

  5. 精尽Spring MVC源码分析 - 一个请求的旅行过程

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  6. spring mvc Response header content type

    Xml代码 <bean class ="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAd ...

  7. Eclipse开发环境设置(Maven+Spring MVC+Flex)

    1. 环境设置 1.1. Java环境设置 1)JAVA_HOME D:\GreenSoftware\Java\Java8X64\jdk1.8.0_91 2)PATH ;%JAVA_HOME%/bin ...

  8. {新人笔记 勿看} spring mvc第一步

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  9. spring mvc获取header

    两种方法: 1.在方法参数中加入@RequestHeader 2.在类级别注入HttpServletRequest 建议使用第二种方法,这样可避免每个方法都加入HttpHeaders参数 @Contr ...

随机推荐

  1. android studio多渠道多包名多apk打包

    转自  利用 Android Studio 和 Gradle 打包多版本APK 搬砖的道路上,经常会有各种不同的需求,比如今天就碰到过一个打包版本的apk的要求, 比如一个apk给多个客户使用,如张三 ...

  2. Andrew Ng机器学习课程笔记--week2(多元线性回归&正规公式)

    1. 内容概要 Multivariate Linear Regression(多元线性回归) 多元特征 多元变量的梯度下降 特征缩放 Computing Parameters Analytically ...

  3. Android 音视频开发(二):使用 AudioRecord 采集音频数据并保存到文件

    版权声明:转载请说明出处:http://www.cnblogs.com/renhui/p/7457321.html 一.AudioRecord API详解 AudioRecord是Android系统提 ...

  4. dede织梦如何防止被黑客入侵渗透?

    dede精简设置篇:避免被hack注射挂马 精简设置篇:不需要的功能统统删除.比如不需要会员就将member文件夹删除.删除多余组件是避免被hack注射的最佳办法.将每个目录添加空的index.htm ...

  5. Web学习-jsp实现servlet过程赏析

    Jsp在某种程度上就是一种servelt. 来看看tomcat容器如何将jsp页面翻译成一个"servlet". 一. F:\apache-tomcat-6.0.51\work\C ...

  6. python+selenium自动化软件测试(第13章):selenium面试题

    前言最近看到群里有小伙伴贴出一组面试题,最近又是跳槽黄金季节,小编忍不住抽出一点时间总结了下 一.selenium中如何判断元素是否存在?expected_conditions模块提供了16种判断方法 ...

  7. 深入剖析C++多态、VPTR指针、虚函数表

    在讲多态之前,我们先来说说关于多态的一个基石------类型兼容性原则. 一.背景知识 1.类型兼容性原则 类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代.通过公有继承,派 ...

  8. 为table元素添加操作日志

    1.为所有的元素添加函数onchange() <input id="status" value="${status}" onchange="ch ...

  9. C++异常处理机制

    商业转载请联系作者获得授权,非商业转载请注明出处. 一.什么是异常 -异常是程序中可以检测到的不正常的情况. -示例:被0除.数组越界.存储空间不足等. -早期我们使用if来判断,然后进行相关处理,或 ...

  10. matlab-常用函数(1)

    rng('shuffle'): matlab help文档中的解释 rng('shuffle'): seeds the random number generator based on the cur ...