SpringMVC Content-Type解析

响应

为了测试方便,我们编写了一个简单的HttpMessageConverter

package cn.bjut.converter;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.util.StreamUtils;

public class MyStringHttpMessageConverter extends AbstractHttpMessageConverter<String> {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");


    public MyStringHttpMessageConverter() {
        this(DEFAULT_CHARSET);
    }

    public MyStringHttpMessageConverter(Charset defaultCharset) {
        super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
    }


    @Override
    public boolean supports(Class<?> clazz) {
        return String.class == clazz;
    }

    @Override
    protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
        Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
        return StreamUtils.copyToString(inputMessage.getBody(), charset);
    }

    @Override
    protected Long getContentLength(String str, MediaType contentType) {
        Charset charset = getContentTypeCharset(contentType);
        try {
            return (long) str.getBytes(charset.name()).length;
        }
        catch (UnsupportedEncodingException ex) {
            // should not occur
            throw new IllegalStateException(ex);
        }
    }

    @Override
    protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {

        Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
        StreamUtils.copy(str, charset, outputMessage.getBody());
    }

    private Charset getContentTypeCharset(MediaType contentType) {
        if (contentType != null && contentType.getCharset() != null) {
            return contentType.getCharset();
        }
        else {
            return getDefaultCharset();
        }
    }

}

以上代码(修改自StringHttpMessageConverter),我们把DEFAULT_CHARSET 即默认的字符集改为UTF-8。并通过构造器传递给父类AbstractHttpMessageConverterdefaultCharset 属性
super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);

为了测试方便,我们把其他的所有消息转换器屏蔽掉

<mvc:annotation-driven>
        <mvc:message-converters register-defaults="false">
            <bean class="cn.bjut.converter.MyStringHttpMessageConverter"/>
        </mvc:message-converters>
    </mvc:annotation-driven>

测试代码:

@Controller
public class TestController {
    @RequestMapping("/test")
    @ResponseBody
    public String test() {
        return "你大爷";
    }
}

debug走起
Fiddler

调用堆栈如下图所示:
调用堆栈

响应头的设置在AbstractHttpMessageConverter 类中

public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        final HttpHeaders headers = outputMessage.getHeaders();
        addDefaultHeaders(headers, t, contentType);
        //...
    }

经测试outputMessage.getHeaders(); 获得的HttpHeaders始终都是空。HttpHeaders实际上就是个Map,用来保存Http Header
public class HttpHeaders implements MultiValueMap<String, String>, Serializable

所以真正的处理在addDefaultHeaders 方法中。

/**
 * 在输出消息中设置响应头
 * MediaType: 形如 text/plain 的媒体类型
*/
protected void addDefaultHeaders(HttpHeaders headers, T t, MediaType contentType) throws IOException{
        if (headers.getContentType() == null) {
            MediaType contentTypeToUse = contentType;
            // 判断媒体类型是否包含通配符*
            if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {
                // 
                contentTypeToUse = getDefaultContentType(t);
            }
            // 判断媒体类型是不是 application/octet-stream
            else if (MediaType.APPLICATION_OCTET_STREAM.equals(contentType)) {
                MediaType mediaType = getDefaultContentType(t);
                contentTypeToUse = (mediaType != null ? mediaType : contentTypeToUse);
            }
            if (contentTypeToUse != null) {
                // 判断媒体类型是否包含字符集(一般的媒体类型形如: "text/plain;charset=UTF-8")
                if (contentTypeToUse.getCharset() == null) {
                    // 设置默认字符集 this.defaultCharset
                    Charset defaultCharset = getDefaultCharset();
                    if (defaultCharset != null) {
                        // 组建媒体类型(一般就形成了: "text/plain;charset=UTF-8")
                        contentTypeToUse = new MediaType(contentTypeToUse, defaultCharset);
                    }
                }
                // 将 Content-Type 添加到Http Header
                headers.setContentType(contentTypeToUse);
            }
        }
        if (headers.getContentLength() < 0 && !headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
            Long contentLength = getContentLength(t, headers.getContentType());
            if (contentLength != null) {
                // 将 Content-Length 添加到Http Header
                headers.setContentLength(contentLength);
            }
        }
    }
protected MediaType getDefaultContentType(T t) throws IOException {
        List<MediaType> mediaTypes = getSupportedMediaTypes();
        return (!mediaTypes.isEmpty() ? mediaTypes.get(0) : null);
    }

以上方法用来获得默认的Content-Type

@Override
    public List<MediaType> getSupportedMediaTypes() {
        return Collections.unmodifiableList(this.supportedMediaTypes);
    }

getSupportedMediaTypes 方法获得属性supportedMediaTypes 保存的媒体类型。

supportedMediaTypes 是一个List集合
private List<MediaType> supportedMediaTypes = Collections.emptyList();

这里写图片描述
可见可以通过AbstractHttpMessageConverter 的子类来设置该属性。
那么既然是一个setter方法,我们也可以自行注入进去的(会覆盖构造函数的设置内容)。

<mvc:annotation-driven>
        <mvc:message-converters register-defaults="false">
            <bean class="cn.bjut.converter.MyStringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/plain;charset=UTF-8</value>
                        <value>text/plain;charset=UTF-8</value>
                        <value>text/plain;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

如代码所示,默认取第一个mediaTypes.get(0)

public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {
        Assert.notEmpty(supportedMediaTypes, "MediaType List must not be empty");
        this.supportedMediaTypes = new ArrayList<MediaType>(supportedMediaTypes);
    }

如上面的代码所示,如果我们自行配置supportedMediaTypes 则会覆盖掉通过构造函数添加进来的。

所以说addDefaultHeaders 方法添加默认Http Headers也就是添加Content-TypeContent-Length
这里写图片描述

请求

关于SpringMVC如何获得请求的Content-Type

在以上MyStringHttpMessageConverter 类中:

protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
        Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
        return StreamUtils.copyToString(inputMessage.getBody(), charset);
    }
private Charset getContentTypeCharset(MediaType contentType) {
        if (contentType != null && contentType.getCharset() != null) {
            return contentType.getCharset();
        }
        else {
            return getDefaultCharset();
        }
    }

如上面代码所示,如果我们在发出请求时没有携带Content-Type
请求头则使用AbstractHttpMessageConverter 里的defaultCharset 属性作为默认的字符集。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

N3verL4nd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值