3. 盘点springmvc的常用接口之HttpMessageConverter###
前言
举例:
POST http://localhost:8080/demo3
传入富文本数据流:Bill Gates
在controller中获得Person对象并响应Person内容:Bill Gates
原始写法:
@RequestMapping(method = RequestMethod.POST)
public void postPerson(HttpServletRequest request, HttpServletResponse response) {
try {
String content = new String(IOUtils.toByteArray(request.getInputStream()));
String[] strs = content.split("\\s");
Person person = new Person(strs[0], strs[1]);
// TODO do something for person
log.info(person.toString());
String text = person.getFirstName() + " " + person.getLastName();
response.getWriter().write(text);
} catch (IOException e) {
e.printStackTrace();
}
}
可以看到原始写法把实体的序列化反序列化过程都堆叠在了controller中,造成controller的混乱和不易阅读。
在springmvc中,我们可以使用@RequestBody
和@ResponseBody
两个注解,分别完成请求报文到对象和对象到响应报文的转换。底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制。
比如我传入的json或者xml格式的报文数据流,要在服务器进行逻辑处理必须事先转换成实体封装。而在进行数据获取请求时,实体对象又要转换成json或xml其他格式的数据流输出。类似这样的实体序列化和反序列化的工作正是由org.springframework.http.converter.HttpMessageConverter
接口实现的。
在springmvc中内置了众多的HttpMessageConverter
实现类,一般情况下无需自定义实现即可满足业务需求。在配置<mvc:annotation-driven/>
或spring-boot的@EnableWebMvc
注解时自动加载如下实现:
org.springframework.http.converter.ByteArrayHttpMessageConverter
org.springframework.http.converter.StringHttpMessageConverter
org.springframework.http.converter.ResourceHttpMessageConverter
org.springframework.http.converter.xml.SourceHttpMessageConverter
org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter
org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
springmvc在使用@RequestBody
接收数据时会依据请求头Content-Type值依次调用上述默认配置的converter的canRead
方法判断该使用哪个转换器。在使用@ResponseBody
响应数据时会依据请求头Accept值依次调用converter的canWrite
方法。所以假如自定义的HttpMessageConverter
有相同的MediaType时需要注册在默认转换器之前。
接口说明
public interface HttpMessageConverter<T> {
//用于检验是否可以读入数据执行read方法
boolean canRead(Class<?> clazz, MediaType mediaType);
//用于检验是否可以写入数据执行write方法
boolean canWrite(Class<?> clazz, MediaType mediaType);
//返回可以支持该消息转换器的Media类型
List<MediaType> getSupportedMediaTypes();
//读入操作,也就是反序列化操作
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
//写出操作,也就是序列化操作
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
泛型T为实体类型。
示例1
改写原始写法
package com.demo.mvc.component;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import com.demo.domain.Person;
public class PersonHttpMessageConverter implements HttpMessageConverter<Person> {
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
if (clazz == Person.class) {
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) {
if (clazz == Person.class) {
if (mediaType == null || MediaType.ALL.equals(mediaType)) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.isCompatibleWith(mediaType)) {
return true;
}
}
}
return false;
}
@Override
public List<MediaType> getSupportedMediaTypes() {
MediaType mediaType = new MediaType("text", "person", Charset.forName("UTF-8"));
return Collections.singletonList(mediaType);
}
@Override
public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
String content = new String(IOUtils.toByteArray(inputMessage.getBody()));
String[] strs = content.split("\\s");
return new Person(strs[0], strs[1]);
}
@Override
public void write(Person person, MediaType mediaType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
String content = person.getFirstName() + " " + person.getLastName();
IOUtils.write(content, outputMessage.getBody());
}
}
注册该消息转换器
spring-boot
package com.demo;
import java.util.List;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import com.demo.mvc.component.PersonHttpMessageConverter;
@SpringBootApplication
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new PersonHttpMessageConverter());
}
}
或xml配置
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.demo.mvc.component.PersonHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
controller
package com.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.demo.domain.Person;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
@RequestMapping("demo3")
public class HttpMessageConverterDemoController {
@ResponseBody
@RequestMapping(method = RequestMethod.POST)
public Person postPerson(@RequestBody Person person) {
log.info(person.toString());
return person;
}
}
示例2
直接实现HttpMessageConverter
接口比较复杂,需要自行处理判断MediaType的逻辑。通常自定义时只需要继承自org.springframework.http.converter.AbstractHttpMessageConverter
抽象类即可,该类已经帮助我们完成了判断MediaType的逻辑。
package com.demo.mvc.component;
import java.io.IOException;
import java.nio.charset.Charset;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import com.demo.domain.Person;
public class PersonHttpMessageConverterExtendsAbstract extends AbstractHttpMessageConverter<Person> {
public PersonHttpMessageConverterExtendsAbstract() {
super(new MediaType("text", "person", Charset.forName("UTF-8")));
}
@Override
protected boolean supports(Class<?> clazz) {
return clazz == Person.class;
}
@Override
protected Person readInternal(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
String content = new String(IOUtils.toByteArray(inputMessage.getBody()));
String[] strs = content.split("\\s");
return new Person(strs[0], strs[1]);
}
@Override
protected void writeInternal(Person person, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
String content = person.getFirstName() + " " + person.getLastName();
IOUtils.write(content, outputMessage.getBody());
}
}
该抽象类提供了supports
方法只需我们验证实体类。
readInternal
方法等同于接口的read
,参看AbstractHttpMessageConverter
源码发现read
直接调用了readInternal
,而writeInternal
也差不多等同于write
,只是当参数HttpOutputMessage
为StreamingHttpOutputMessage
时另行处理了。
AbstractHttpMessageConverter
源码:
/**
* This implementation simple delegates to {@link #readInternal(Class, HttpInputMessage)}.
* Future implementations might add some default behavior, however.
*/
@Override
public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException {
return readInternal(clazz, inputMessage);
}
/**
* This implementation sets the default headers by calling {@link #addDefaultHeaders},
* and then calls {@link #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();
}
}
友情链接: