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

So easy, 看下面的代码:

  1. @RequestMapping(value = "/rulelist", method = RequestMethod.GET)
  2.  
  3. @ResponseBody
  4. public String getRuleList(HttpServletRequest request,
  5. HttpServletResponse response) {
  6. response.addHeader("test", "test");
  7. return service.getRuleList();
  8. }

通过验证,我们可以看到test项已经被成功添加到response的头部信息

  1. Content-Length: 2 kilobytes
  2. Content-Type: text/plain;charset=ISO-8859-1
  3. Server: Apache-Coyote/1.1
  4. test: test

接下来,我们希望修改Content-Type,从而统一服务器端和客户端的内容编码。我们继续修改代码,

  1. @RequestMapping(value = "/rulelist", method = RequestMethod.GET)
  2. @ResponseBody
  3. public String getRuleList(HttpServletRequest request,
  4. HttpServletResponse response) {
  5. response.addHeader("Content-Type", "application/json;charset=UTF-8");
  6. return service.getRuleList();
  7. }

接下来,我们验证一下结果:

  1. Content-Length: 2 kilobytes
  2. Content-Type: text/plain;charset=ISO-8859-1
  3. 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):

  1. protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
  2. ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
  3. throws IOException, HttpMediaTypeNotAcceptableException {
  4.  
  5. Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
  6. HttpServletRequest servletRequest = inputMessage.getServletRequest();
  7. List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest); //适合的兼容media types类型实际上,我们可以使用produces = {}来指定我们需要的mediatype
  8. List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
  9.  
  10. Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
  11. for (MediaType requestedType : requestedMediaTypes) {
  12. for (MediaType producibleType : producibleMediaTypes) {
  13. if (requestedType.isCompatibleWith(producibleType)) {
  14. compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
  15. }
  16. }
  17. }
  18. if (compatibleMediaTypes.isEmpty()) {
  19. if (returnValue != null) {
  20. throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
  21. }
  22. return;
  23. }
  24.  
  25. List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
  26. MediaType.sortBySpecificityAndQuality(mediaTypes);
  27.  
  28. MediaType selectedMediaType = null;   //选择最匹配的mediaType
  29. for (MediaType mediaType : mediaTypes) {
  30. if (mediaType.isConcrete()) {
  31. selectedMediaType = mediaType;
  32. break;
  33. }
  34. else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
  35. selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
  36. break;
  37. }
  38. }
  39.  
  40. if (selectedMediaType != null) {
  41. selectedMediaType = selectedMediaType.removeQualityValue();
  42. for (HttpMessageConverter<?> messageConverter : this.messageConverters) { //遍历messageConvertors, 寻找可以处理相应返回类型和mediatype的HttpMessageConvertor
  43. if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
  44. returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
  45. (Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
  46. if (returnValue != null) { //这里将会填充mediatype到header,并将httpmessage发送给请求者
  47. ((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
  48. if (logger.isDebugEnabled()) {
  49. logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
  50. messageConverter + "]");
  51. }
  52. }
  53. return;
  54. }
  55. }
  56. }
  57.  
  58. if (returnValue != null) {
  59. throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
  60. }
  61. }

接下来,将选择好的mediatype写入到HttpOutputMessage中

  1. public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)
  2. throws IOException, HttpMessageNotWritableException {
  3.  
  4. final HttpHeaders headers = outputMessage.getHeaders(); //设置contenttype到HttpOutputMessage
  5. if (headers.getContentType() == null) {
  6. MediaType contentTypeToUse = contentType;
  7. if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {
  8. contentTypeToUse = getDefaultContentType(t);
  9. }
  10. if (contentTypeToUse != null) {
  11. headers.setContentType(contentTypeToUse);
  12. }
  13. }
  14. if (headers.getContentLength() == -1) {
  15. Long contentLength = getContentLength(t, headers.getContentType());
  16. if (contentLength != null) {
  17. headers.setContentLength(contentLength);
  18. }
  19. }
  20.       /* 省略了不相干代码 */
  21. }

最终的Headers设置在ServletServerHttpResponse类中完成,

  1. private void writeHeaders() {
  2. if (!this.headersWritten) {
  3. for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
  4. String headerName = entry.getKey();
  5. for (String headerValue : entry.getValue()) { //将复合类中之前设置的header(content-type)内容补充到servletResponse
  6. this.servletResponse.addHeader(headerName, headerValue);
  7. }
  8. }
  9. // HttpServletResponse exposes some headers as properties: we should include those if not already present
  10. if (this.servletResponse.getContentType() == null && this.headers.getContentType() != null) {
  11. this.servletResponse.setContentType(this.headers.getContentType().toString());
  12. }
  13. if (this.servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null &&
  14. this.headers.getContentType().getCharSet() != null) {
  15. this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharSet().name());
  16. }
  17. this.headersWritten = true;
  18. }
  19. }

从上述的代码中,我们可以看到在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)
@ResponseBody
public String getRuleList(HttpServletRequest request,
        HttpServletResponse response) {
    response.addHeader("test""test");
    return service.getRuleList();
}

通过验证,我们可以看到test项已经被成功添加到response的头部信息

1
2
3
4
Content-Length: 2 kilobytes
Content-Type:   text/plain;charset=ISO-8859-1
Server: Apache-Coyote/1.1
test: test

接下来,我们希望修改Content-Type,从而统一服务器端和客户端的内容编码。我们继续修改代码,

1
2
3
4
5
6
7
@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();
}

接下来,我们验证一下结果:

1
2
3
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):

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的更多相关文章

  1. Spring MVC能响应HTTP请求的原因?

    很多Java面试官喜欢问这个问题: 一个Spring MVC的项目文件里,开发人员没有开发自己的Servlet,只通过注解@RequestMapping定义了方法home能响应发向 /mvc/test ...

  2. Spring MVC @ResponseBody响应中文乱码

    问题:在前端通过get请求服务端返回String类型的服务时,会出现中文乱码问题 原因:由于spring默认对String类型的返回的编码采用的是 StringHttpMessageConverter ...

  3. Spring MVC执行的流程

    1.Spring MVC应用的开发步骤 a.在web.xml文件中定义前端控制器DispatcherServlet来拦截用户请求.由于Web应用是基于请求/响应架构的应用,所以 不管哪个MVC Web ...

  4. Spring MVC 简述:从MVC框架普遍关注的问题说起

    任何一个完备的MVC框架都需要解决Web开发过程中的一些共性的问题,比如请求的收集与分发.数据前后台流转与转换,当前最流行的SpringMVC和Struts2也不例外.本文首先概述MVC模式的分层思想 ...

  5. Spring MVC详细讲解

    一:三层架构和MVC 1:三层架构 我们的开发架构一般都是基于两种形式:一种是 C/S 架构,也就是客户端/服务器,另一种是 B/S 架构,也就是浏览器服务器.在 JavaEE 开发中,几乎全都是基于 ...

  6. 前端控制器是整个MVC框架中最为核心的一块,它主要用来拦截符合要求的外部请求,并把请求分发到不同的控制器去处理,根据控制器处理后的结果,生成相应的响应发送到客户端。前端控制器既可以使用Filter实现(Struts2采用这种方式),也可以使用Servlet来实现(spring MVC框架)。

    本文转自http://www.cnblogs.com/davidwang456/p/4090058.html 感谢作者 前端控制器是整个MVC框架中最为核心的一块,它主要用来拦截符合要求的外部请求,并 ...

  7. 从content-type设置看Spring MVC处理header的一个坑

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

  8. spring mvc 通过拦截器记录请求数据和响应数据

    spring mvc 能过拦截器记录请求数据记录有很多种方式,主要有以下三种: 1:过滤器 2:HandlerInterceptor拦截器 3:Aspect接口控制器 但是就我个人所知要记录返回的数据 ...

  9. spring MVC 返回值信息和ResponseBody的响应json数据

    spring mvc的界面返回: 如果我们定义的返回类型是String 那么我们返回的时候直接写入 我们的界面的名字就可以了  springmvc会自动去找到我们的界面,如果是void类型的返回那么 ...

随机推荐

  1. js中的let\var\const

    在JavaScript中有三种声明变量的方式:var.let.const.下文给大家介绍js中三种定义变量的方式const, var, let的区别. 1.const定义的变量不可以修改,而且必须初始 ...

  2. 第二次结对作业-WordCount进阶需求

    原博客 队友博客 github项目地址 目录 具体分工 需求分析 PSP表格 解题思路描述与设计实现说明 爬虫使用 代码组织与内部实现设计(类图) 算法的关键与关键实现部分流程图 附加题设计与展示 设 ...

  3. 实测 | 转型微服务,这4大工具谁是API网关性能最优?

    转自:http://www.servicemesh.cn/?/article/45 作者:Turgay Çelik 翻译:钟毅(Drew Zhong) 原文:Comparing API Gateway ...

  4. [日常工作] 应用服务器上面应该尽量少开各种应用 --Chrome 内存泄露 让应用服务器非常缓慢

    1. 前段时间修改 服务器的密码 导致应用程序的web site 启动有问题 ,打开chrome 查看了错误详细信息 但是忘记关了.. 今天反馈机器非常缓慢 简单看了下内存 吐血... 所以以后不能在 ...

  5. list 交换位置扩展

    public static List<T> Swap<T>(this List<T> list, int index1,int index2) { if(index ...

  6. QProcess 进程调用

    1. 调用方的接口: void QProcess::start(const QString &program, const QStringList &arguments, OpenMo ...

  7. CodeForces - 988C(STL大法好)

    请你找出两个编号不同的数列,并从这两个数列中各恰好删除一个数,使得这两个数列的和相等. 用vector存每一个数 用map标记 即可 #include <bits/stdc++.h> us ...

  8. Pythonの坑

    Python closures and late binding A closure occurs when a function has access to a local variable fro ...

  9. 【bzoj1396】 识别子串

    http://www.lydsy.com/JudgeOnline/problem.php?id=1396 (题目链接) 题意 问字符串S每一位的最短识别子串是多长(识别子串指包含这个字符且只出现在S中 ...

  10. Mac显示同一程序的所有窗口

    通过F3键可以显示所有程序的所有窗口到桌面,但是如果窗口太多的话显示出来很难看清. 如果只显示某一个程序的所有窗口就不会乱了. 其实在dock上有一个相应的选项,但是没有快捷键. 开启方法: 在终端上 ...