SpringMVC-StringHttpMessageConverter简介

来源:互联网 发布:萧山网络问政平台临浦 编辑:程序博客网 时间:2024/06/07 00:10

旨在通过分析StringHttpMessageConverter 来初步认识消息转换器HttpMessageConverter 的处理流程。分析完StringHttpMessageConverter 便可以窥视SpringMVC消息处理的庐山真面目了。


/** * HttpMessageConverter 的实现类:完成请求报文到字符串和字符串到响应报文的转换 * 默认情况下,此转换器支持所有媒体类型(*&#47;*),并使用 Content-Type 为 text/plain 的内容类型进行写入 * 这可以通过 setSupportedMediaTypes(父类 AbstractHttpMessageConverter 中的方法) 方法设置 supportedMediaTypes 属性来覆盖 */public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {    // 默认字符集(产生乱码的根源)    public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");    //可使用的字符集    private volatile List<Charset> availableCharsets;    //标识是否输出 Response Headers:Accept-Charset(默认输出)    private boolean writeAcceptCharset = true;    /**     * 使用 "ISO-8859-1" 作为默认字符集的默认构造函数     */    public StringHttpMessageConverter() {        this(DEFAULT_CHARSET);    }    /**     * 如果请求的内容类型 Content-Type 没有指定一个字符集,则使用构造函数提供的默认字符集     */    public StringHttpMessageConverter(Charset defaultCharset) {        super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);    }    /**     * 标识是否输出 Response Headers:Accept-Charset     * 默认是 true     */    public void setWriteAcceptCharset(boolean writeAcceptCharset) {        this.writeAcceptCharset = writeAcceptCharset;    }    @Override    public boolean supports(Class<?> clazz) {        return String.class == clazz;    }    /**     * 将请求报文转换为字符串    */    @Override    protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {        //通过读取请求报文里的 Content-Type 来获取字符集        Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());        //调用 StreamUtils 工具类的 copyToString 方法来完成转换        return StreamUtils.copyToString(inputMessage.getBody(), charset);    }    /**     * 返回字符串的大小(转换为字节数组后的大小)     * 依赖于 MediaType 提供的字符集    */    @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 {        //输出 Response Headers:Accept-Charset(默认输出)        if (this.writeAcceptCharset) {            outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());        }        Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());        //调用 StreamUtils 工具类的 copy 方法来完成转换        StreamUtils.copy(str, charset, outputMessage.getBody());    }    /**     * 返回所支持的字符集     * 默认返回 Charset.availableCharsets()     * 子类可以覆盖该方法     */    protected List<Charset> getAcceptedCharsets() {        if (this.availableCharsets == null) {            this.availableCharsets = new ArrayList<Charset>(                    Charset.availableCharsets().values());        }        return this.availableCharsets;    }    /**     * 获得 ContentType 对应的字符集     */    private Charset getContentTypeCharset(MediaType contentType) {        if (contentType != null && contentType.getCharset() != null) {            return contentType.getCharset();        }        else {            return getDefaultCharset();        }    }}

解读:

private boolean writeAcceptCharset = true;
是说是否输出以下内容:
这里写图片描述

可以使用如下配置屏蔽它:

<mvc:annotation-driven>        <mvc:message-converters>            <bean id="messageConverter" class="org.springframework.http.converter.StringHttpMessageConverter">                <property name="writeAcceptCharset" value="false"/>            </bean>        </mvc:message-converters>    </mvc:annotation-driven>

private volatile List<Charset> availableCharsets;
没有看到使用场合。

使用 text/plain 写出,也就是返回响应报文,其实也是不准确的。
chrome
这里写图片描述
可以看到客户端的不同导致输出也不同。
测试下:
这里写图片描述
这里写图片描述

可以看到响应报文里的Content-Type依赖于请求报文里的Accept。
那么当我们指定带编码的Accept 能否解决乱码问题呢?
这里写图片描述
其实很简单的道理,你他丫的希望接受的数据类型是Accept: text/plain;charset=UTF-8,我他丫的发送的数据类型Content-Type: text/plain;charset=UTF-8 当然也要保持一致。

StringHttpMessageConverter的哲学便是:你想要什么类型的数据,我便发送给你该类型的数据。


在操蛋的Windows操作系统上处理编解码问题是真的操蛋!
cmd下 chcp 65001 或者使用Cygwin都他妈的各种非正常乱码
索性去Ubuntu测试去了。

@RequestMapping(value = "/testCharacter", method = RequestMethod.POST)    @ResponseBody    public String testCharacter2(@RequestBody String str) {        System.out.println(str);        return "你大爷";    }

curl -H "Content-Type: text/plain; charset=UTF-8" -H "Accept: text/plain; charset=UTF-8" -d "你大爷"
http://localhost:8080/SpringMVCDemo/testCharacter

Jetty容器输出:你大爷
控制台输出:你大爷

curl -H "Accept: text/plain; charset=UTF-8" -d "你大爷"
http://localhost:8080/SpringMVCDemo/testCharacter

Jetty容器输出:%E4%BD%A0%E5%A4%A7%E7%88%B7
控制台输出:你大爷

%E4%BD%A0%E5%A4%A7%E7%88%B7 使用了URL编码解码后还是字符串你大爷

curl -H "Content-Type: text/plain; charset=UTF-8" -d "你大爷"
http://localhost:8080/SpringMVCDemo/testCharacter

Jetty容器输出:你大爷
控制台输出:???

原理通过读一下代码就清楚了:

@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 void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {        if (this.writeAcceptCharset) {            outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());        }        Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());        StreamUtils.copy(str, charset, outputMessage.getBody());    }

而以往我们解决乱码问题的办法形如:

@RequestMapping(value = "/test1", method = RequestMethod.POST)    @ResponseBody    public void test1(HttpServletRequest request) throws IOException {        InputStream in = request.getInputStream();        byte[] buffer = new byte[in.available()];        in.read(buffer);        in.close();        String str = new String(buffer, "gb2312");        System.out.println(str);    }

这里写图片描述

以什么格式输入的字符串,就得以相应的格式进行转换。

/** * 实现 HttpMessageConverter 的抽象基类 * * 该基类通过 Bean 属性 supportedMediaTypes 添加对自定义 MediaTypes 的支持 * 在输出响应报文时,它还增加了对 Content-Type 和 Content-Length 的支持 */public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {    /** Logger 可用于子类 */    protected final Log logger = LogFactory.getLog(getClass());    // 存放支持的 MediaType(媒体类型)的集合    private List<MediaType> supportedMediaTypes = Collections.emptyList();    // 默认字符集    private Charset defaultCharset;    /**     * 默认构造函数     */    protected AbstractHttpMessageConverter() {    }    /**     * 构造一个带有一个支持的 MediaType(媒体类型)的 AbstractHttpMessageConverter     */    protected AbstractHttpMessageConverter(MediaType supportedMediaType) {        setSupportedMediaTypes(Collections.singletonList(supportedMediaType));    }    /**     * 构造一个具有多个支持的 MediaType(媒体类型)的 AbstractHttpMessageConverter     */    protected AbstractHttpMessageConverter(MediaType... supportedMediaTypes) {        setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));    }    /**     * 构造一个带有默认字符集和多个支持的媒体类型的 AbstractHttpMessageConverter     */    protected AbstractHttpMessageConverter(Charset defaultCharset, MediaType... supportedMediaTypes) {        this.defaultCharset = defaultCharset;        setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));    }    /**     * 设置此转换器支持的 MediaType 对象集合     */    public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {        // 断言集合 supportedMediaTypes 是否为空        Assert.notEmpty(supportedMediaTypes, "MediaType List must not be empty");        this.supportedMediaTypes = new ArrayList<MediaType>(supportedMediaTypes);    }    @Override    public List<MediaType> getSupportedMediaTypes() {        return Collections.unmodifiableList(this.supportedMediaTypes);    }    /**     * 设置默认字符集     */    public void setDefaultCharset(Charset defaultCharset) {        this.defaultCharset = defaultCharset;    }    /**     * 返回默认字符集     */    public Charset getDefaultCharset() {        return this.defaultCharset;    }    /**     * 该实现检查该转换器是否支持给定的类,以及支持的媒体类型集合是否包含给定的媒体类型     */    @Override    public boolean canRead(Class<?> clazz, MediaType mediaType) {        return supports(clazz) && canRead(mediaType);    }    /**     * 如果该转换器所支持的媒体类型集合包含给定的媒体类型,则返回true     * mediaType: 要读取的媒体类型,如果未指定,则可以为null。 通常是 Content-Type 的值     */    protected boolean canRead(MediaType mediaType) {        if (mediaType == null) {            return true;        }        for (MediaType supportedMediaType : getSupportedMediaTypes()) {            if (supportedMediaType.includes(mediaType)) {                return true;            }        }        return false;    }    /**     * 该实现检查该转换器是否支持给定的类,以及支持的媒体类型集合是否包含给定的媒体类型     */    @Override    public boolean canWrite(Class<?> clazz, MediaType mediaType) {        return supports(clazz) && canWrite(mediaType);    }    /**     * 如果给定的媒体类型包含任何支持的媒体类型,则返回true     * mediaType: 要写入的媒体类型,如果未指定,则可以为null。通常是 Accept 的值     * 如果支持的媒体类型与传入的媒体类型兼容,或媒体类型为空,则返回 true     */    protected boolean canWrite(MediaType mediaType) {        if (mediaType == null || MediaType.ALL.equals(mediaType)) {            return true;        }        for (MediaType supportedMediaType : getSupportedMediaTypes()) {            if (supportedMediaType.isCompatibleWith(mediaType)) {                return true;            }        }        return false;    }    /**     * readInternal(Class, HttpInputMessage) 的简单代理方法     * 未来的实现可能会添加一些默认行为     */    @Override    public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException {        return readInternal(clazz, inputMessage);    }    /**     * 该实现通过调用 addDefaultHeaders 来设置默认头文件,然后调用 writeInternal 方法     */    @Override    public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)            throws IOException, HttpMessageNotWritableException {        final HttpHeaders headers = outputMessage.getHeaders();        addDefaultHeaders(headers, t, contentType);        if (outputMessage instanceof StreamingHttpOutputMessage) {            StreamingHttpOutputMessage streamingOutputMessage =                    (StreamingHttpOutputMessage) outputMessage;            streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {                @Override                public void writeTo(final OutputStream outputStream) throws IOException {                    writeInternal(t, new HttpOutputMessage() {                        @Override                        public OutputStream getBody() throws IOException {                            return outputStream;                        }                        @Override                        public HttpHeaders getHeaders() {                            return headers;                        }                    });                }            });        }        else {            writeInternal(t, outputMessage);            outputMessage.getBody().flush();        }    }    /**     * 将默认 HTTP Headers 添加到响应报文     */    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);            }            else if (MediaType.APPLICATION_OCTET_STREAM.equals(contentType)) {                MediaType mediaType = getDefaultContentType(t);                contentTypeToUse = (mediaType != null ? mediaType : contentTypeToUse);            }            if (contentTypeToUse != null) {                if (contentTypeToUse.getCharset() == null) {                    Charset defaultCharset = getDefaultCharset();                    if (defaultCharset != null) {                        contentTypeToUse = new MediaType(contentTypeToUse, defaultCharset);                    }                }                //设置Content-Type                headers.setContentType(contentTypeToUse);            }        }        if (headers.getContentLength() < 0 && !headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {            Long contentLength = getContentLength(t, headers.getContentType());            if (contentLength != null) {                //设置Content-Length                headers.setContentLength(contentLength);            }        }    }    /**     * 返回给定类型的默认内容类型     * 当 write(final T t, MediaType contentType, HttpOutputMessage outputMessage) 的 MediaType     * 为 null 时,被调用     * 默认情况下,这将返回 supportedMediaTypes 集合中的第一个元素(如果有)     * 可以在子类中被覆盖     */    protected MediaType getDefaultContentType(T t) throws IOException {        List<MediaType> mediaTypes = getSupportedMediaTypes();        return (!mediaTypes.isEmpty() ? mediaTypes.get(0) : null);    }    /**     * 返回给定类型(字符集)的内容长度     */    protected Long getContentLength(T t, MediaType contentType) throws IOException {        return null;    }    /**     * 指示该转换器是否支持给定的类     */    protected abstract boolean supports(Class<?> clazz);    /**     * 抽象模板方法:读取实际对象     */    protected abstract T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)            throws IOException, HttpMessageNotReadableException;    /**     * 抽象模板方法: 输出响应报文     */    protected abstract void writeInternal(T t, HttpOutputMessage outputMessage)            throws IOException, HttpMessageNotWritableException;}
原创粉丝点击