spring mvc content-type设置规则

经查询资料

<!-- Make this available across all of Spring MVC -->
 <mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
  <bean id="contentNegotiationManager"
             class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
       <property name="defaultContentType" value="application/json;charset=UTF-8" />
  </bean>

可设置默认response 相应类型

-------------------------------------------------------------------------------------------------------------------------------------------------------------

我们经常需要在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):

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值