为了测试方便,我们编写了一个简单的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
。并通过构造器传递给父类AbstractHttpMessageConverter
的defaultCharset
属性
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 "你大爷";
}
}
响应头的设置在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
所以真正的处理在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 mediaTypes = getSupportedMediaTypes();
return (!mediaTypes.isEmpty() ? mediaTypes.get(0) : null);
}
以上方法用来获得默认的Content-Type
@Override
public List getSupportedMediaTypes() {
return Collections.unmodifiableList(this.supportedMediaTypes);
}
getSupportedMediaTypes
方法获得属性supportedMediaTypes
保存的媒体类型。
supportedMediaTypes
是一个List集合
private List
可见可以通过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-8value>
<value>text/plain;charset=UTF-8value>
<value>text/plain;charset=UTF-8value>
list>
property>
bean>
mvc:message-converters>
mvc:annotation-driven>
如代码所示,默认取第一个mediaTypes.get(0)
。
public void setSupportedMediaTypes(List supportedMediaTypes) {
Assert.notEmpty(supportedMediaTypes, "MediaType List must not be empty");
this.supportedMediaTypes = new ArrayList(supportedMediaTypes);
}
如上面的代码所示,如果我们自行配置supportedMediaTypes
则会覆盖掉通过构造函数添加进来的。
所以说addDefaultHeaders
方法添加默认Http Headers也就是添加Content-Type
与 Content-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
属性作为默认的字符集。