MIME类型
MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
在万维网中使用的HTTP协议中也使用了MIME的框架,它使得HTTP传输的不仅是普通的文本,而变得丰富多彩。
在HTTP中,MIME类型被定义在Content-Type header中。
image imageHttpMessageConverter简介
HTTP 请求和响应的传输是字节流,意味着浏览器和服务器通过字节流进行通信。但是,使用 Spring,controller 类中的方法返回纯 String 类型或其他 Java 内建对象。如何将对象转换成字节流进行传输?
在报文到达SpringMVC和从SpringMVC出去,都存在一个字节流到java对象的转换问题。在SpringMVC中,它是由HttpMessageConverter来处理的。
当请求报文来到java中,它会被封装成为一个ServletInputStream的输入流,供我们读取报文。响应报文则是通过一个ServletOutputStream的输出流,来输出响应报文。
我们可以用下图来加深理解。
imageHttpMessageConverter接口
在Spring中,内置了大量的HttpMessageConverter。通过请求头信息中的MIME类型,选择相应的HttpMessageConverter。
它们都实现了HttpMessageConverter这个接口。
接口的代码如下
public interface HttpMessageConverter<T> {
booleancanRead(Class<?>clazz, MediaTypemediaType);
booleancanWrite(Class<?>clazz, MediaTypemediaType);
List<MediaType>getSupportedMediaTypes();
T read(Class<? extends T>clazz, HttpInputMessageinputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, MediaTypecontentType, HttpOutputMessageoutputMessage)
throws IOException, HttpMessageNotWritableException;
}
HttpMessageConverter接口的定义中出现了成对的canRead(),read()和canWrite(),write()方法。MediaType是对请求的Media Type属性的封装。
read方法中有一个HttpInputMessage,我们查看它的源码如下。
public interface HttpInputMessageextends HttpMessage {
InputStreamgetBody() throws IOException;
}
HttpInputMessage提供的接口就是将body中的数据转为输入流。
write方法中有一个HttpOutputMessage,我们查看它的源码如下。
public interface HttpOutputMessageextends HttpMessage {
OutputStreamgetBody() throws IOException;
}
HttpOutputMessage提供的接口就是将body中的数据转为输出流。
它们拥有相同的父接口HttpMessage。
public interface HttpMessage {
HttpHeadersgetHeaders();
}
HttpMessage提供的方法是读取头部中的信息。
HttpMessageConverter的工作过程
当我们声明了下面这个处理方法。
@RequestMapping(value="/string", method=RequestMethod.POST)
public @ResponseBodyString readString(@RequestBody String string) {
return "Read string '" + string + "'";
}
在SpringMVC进入readString方法前,会根据@RequestBody注解选择适当的HttpMessageConverter实现类来将请求参数解析到string变量中,具体来说是使用了StringHttpMessageConverter类,它的canRead()方法返回true,然后它的read()方法会从请求中读出请求参数,绑定到readString()方法的string变量中。
当SpringMVC执行readString方法后,由于返回值标识了@ResponseBody,SpringMVC将使用StringHttpMessageConverter的write()方法,将结果作为String值写入响应报文,当然,此时canWrite()方法返回true。
Spring中内置的一部分HttpMessageConverter如下。
各种HttpMessageConverter
处理请求时,由合适的消息转换器将请求报文绑定为方法中的形参对象,在这里,同一个对象就有可能出现多种不同的消息形式,比如json和xml。同样,当响应请求时,方法的返回值也同样可能被返回为不同的消息形式,比如json和xml。
在SpringMVC中,针对不同的消息形式,我们有不同的HttpMessageConverter实现类来处理各种消息形式。至于各种消息间解析细节的不同,就被屏蔽在不同的HttpMessageConverter实现类中了。
自定义HttpMessageConverter
在颠覆者中,我们进行了自定义HttpMessageConverter,当时我们是这么做的。
public class MyMessageConverterextends AbstractHttpMessageConverter<DemoObj>
查看AbstractHttpMessageConverter的源码。
public abstract class AbstractHttpMessageConverter<T>implements HttpMessageConverter<T>
可以看到实现了HttpMessageConverter接口,可以说这个接口是HttpMessageConverter的核心。
@Override
protected DemoObjreadInternal(Class<? extends DemoObj>clazz,
HttpInputMessageinputMessage) throws IOException,
HttpMessageNotReadableException {
String temp = StreamUtils.copyToString(inputMessage.getBody(),
Charset.forName("UTF-8"));
String[] tempArr = temp.split("-");
return new DemoObj(new Long(tempArr[0]), tempArr[1]);
}
读进来转换成对象。
@Override
protected void writeInternal(DemoObjobj, HttpOutputMessageoutputMessage)
throws IOException, HttpMessageNotWritableException {
String out = "hello:" + obj.getId() + "-"
+ obj.getName();
outputMessage.getBody().write(out.getBytes());
}
将对象输出去。
问题
在ConverterController中定义了映射"/convert"。
然而,在地址栏中访问http://localhost:8080/convert?id=2&name=wang会报错“Required request body content is missing:……”,为什么?如何避免这一错误?
问题解决
原因是在地址栏中输入url进行访问是get请求,
而@RequestBody注解对get请求并不适用,而是要将参数放在请求体中。注释掉@RequestBody注解即可避免这一错误。
但是注释掉@RequestBody注解后,会发现返回的信息被下载下来了。
@RequestMapping(value = "/convert", produces = { "application/x-wisely" })
这是因为响应的格式被定义成了我们自定义的类型,而在MyMessageConverter中输出到outPutMessage时我们并没有设置Content-type。
@Override
protected void writeInternal(DemoObj obj, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException{
String out = "hello:" + obj.getId() + "-"+ obj.getName();
outputMessage.getHeaders().setContentType(MediaType.TEXT_PLAIN);
outputMessage.getBody().write(out.getBytes());
}
这样设置后就能显示在浏览器页面上了。
另一种解决方案
使用@RequestBody注解,发送POST请求,把参数放在request体中,用例子中的converter页面,用ajax发送post请求后成功回调函数中把获取的data输出到页面上,这样即使没有设置HttpOutputMessage响应头的content-type也能把信息输出到页面上。
function req(){
$.ajax({
url: "convert",
data: "1-wangyunfei", //1
type:"POST",
contentType:"application/x-wisely", //2
success: function(data){
$("#resp").html(data);
}
});
}
运行效果
网友评论