举例:java
POST http://localhost:8080/demo3web
传入富文本数据流:Bill Gatesspring
在controller中得到Person对象并响应Person内容:Bill Gatesapache
原始写法:json
@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即消息转换器机制。mvc
好比我传入的json或者xml格式的报文数据流,要在服务器进行逻辑处理必须事先转换成实体封装。而在进行数据获取请求时,实体对象又要转换成json或xml其余格式的数据流输出。相似这样的实体序列化和反序列化的工做正是由org.springframework.http.converter.HttpMessageConverter
接口实现的。app
在springmvc中内置了众多的HttpMessageConverter
实现类,通常状况下无需自定义实现便可知足业务需求。在配置<mvc:annotation-driven/>
或spring-boot的@EnableWebMvc
注解时自动加载以下实现:dom
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时须要注册在默认转换器以前。ide
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为实体类型。
改写原始写法
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; } }
直接实现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(); } }
友情连接: