舉例: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(); } }
友情連接: