HttpMessageConverter接口介紹:
- public interface HttpMessageConverter<T> {
- /**
- * Indicates whether the given class can be read by this converter.
- * @param clazz the class to test for readability
- * @param mediaType the media type to read, can be {@code null} if not specified.
- * Typically the value of a {@code Content-Type} header.
- * @return {@code true} if readable; {@code false} otherwise
- */
- boolean canRead(Class<?> clazz, MediaType mediaType);
- /**
- * Indicates whether the given class can be written by this converter.
- * @param clazz the class to test for writability
- * @param mediaType the media type to write, can be {@code null} if not specified.
- * Typically the value of an {@code Accept} header.
- * @return {@code true} if writable; {@code false} otherwise
- */
- boolean canWrite(Class<?> clazz, MediaType mediaType);
- /**
- * Return the list of {@link MediaType} objects supported by this converter.
- * @return the list of supported media types
- */
- List<MediaType> getSupportedMediaTypes();
- /**
- * Read an object of the given type form the given input message, and returns it.
- * @param clazz the type of object to return. This type must have previously been passed to the
- * {@link #canRead canRead} method of this interface, which must have returned {@code true}.
- * @param inputMessage the HTTP input message to read from
- * @return the converted object
- * @throws IOException in case of I/O errors
- * @throws HttpMessageNotReadableException in case of conversion errors
- */
- T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
- throws IOException, HttpMessageNotReadableException;
- /**
- * Write an given object to the given output message.
- * @param t the object to write to the output message. The type of this object must have previously been
- * passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
- * @param contentType the content type to use when writing. May be {@code null} to indicate that the
- * default content type of the converter must be used. If not {@code null}, this media type must have
- * previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
- * returned {@code true}.
- * @param outputMessage the message to write to
- * @throws IOException in case of I/O errors
- * @throws HttpMessageNotWritableException in case of conversion errors
- */
- void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
- throws IOException, HttpMessageNotWritableException;
- }
從HttpInputMessage中讀取數據: T read(Class<? extends T> clazz, HttpInputMessage inputMessage),前提clazz能夠通過canRead(clazz,mediaType)測試。
向HttpOutputMessage中寫入數據:void write(T t, MediaType contentType, HttpOutputMessage outputMessage),前提能夠通過canWrite方法。
簡單舉例:
如StringHttpMessageConverter,read方法就是根據編碼類型將HttpInputMessage中的數據變爲字符串。write方法就是根據編碼類型將字符串數據寫入HttpOutputMessage中。
HttpMessageConverter的使用場景:
它主要是用來轉換request的內容到一定的格式,轉換輸出的內容的到response。
看下自定義的使用方式:
- <mvc:annotation-driven>
- <mvc:message-converters register-defaults="true">
- <bean class="org.springframework.http.converter.StringHttpMessageConverter">
- <constructor-arg value="UTF-8"/>
- </bean>
- </mvc:message-converters>
- </mvc:annotation-driven>
首先還是在對mvc:annotation-driven解析的AnnotationDrivenBeanDefinitionParser中,有這麼一個方法:
- ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
獲取所有的HttpMessageConverter,最終設置到RequestMappingHandlerAdapter的private List<HttpMessageConverter<?>> messageConverters屬性上。看下具體的獲取過程:
- private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) {
- Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
- ManagedList<? super Object> messageConverters = new ManagedList<Object>();
- if (convertersElement != null) {
- messageConverters.setSource(source);
- for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {
- Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);
- messageConverters.add(object);
- }
- }
- if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
- messageConverters.setSource(source);
- messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));
- RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
- stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
- messageConverters.add(stringConverterDef);
- messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
- messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
- messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));
- if (romePresent) {
- messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
- messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
- }
- if (jaxb2Present) {
- messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
- }
- if (jackson2Present) {
- messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));
- }
- else if (jacksonPresent) {
- messageConverters.add(createConverterDefinition(
- org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.class, source));
- }
- }
- return messageConverters;
- }
該過程第一步:
解析並獲取我們自定義的HttpMessageConverter,
該過程第二步:
<mvc:message-converters register-defaults="true">有一個register-defaults屬性,當爲true時,仍然註冊默認的HttpMessageConverter,當爲false則不註冊,僅僅使用用戶自定義的HttpMessageConverter。
獲取完畢,便會將這些HttpMessageConverter設置進RequestMappingHandlerAdapter的messageConverters屬性中。
然後就是它的使用過程,HttpMessageConverter主要針對那些不會返回view視圖的response:
即含有方法含有@ResponseBody或者返回值爲HttpEntity等類型的,它們都會用到HttpMessageConverter。以@ResponseBody舉例:
首先先決定由哪個HandlerMethodReturnValueHandler來處理返回值,由於是@ResponseBody所以將會由RequestResponseBodyMethodProcessor來處理,然後就是如下的寫入:
- protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
- ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
- throws IOException, HttpMediaTypeNotAcceptableException {
- Class<?> returnValueClass = returnValue.getClass();
- HttpServletRequest servletRequest = inputMessage.getServletRequest();
- List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
- List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
- Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
- for (MediaType requestedType : requestedMediaTypes) {
- for (MediaType producibleType : producibleMediaTypes) {
- if (requestedType.isCompatibleWith(producibleType)) {
- compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
- }
- }
- }
- if (compatibleMediaTypes.isEmpty()) {
- throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
- }
- List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
- MediaType.sortBySpecificityAndQuality(mediaTypes);
- MediaType selectedMediaType = null;
- for (MediaType mediaType : mediaTypes) {
- if (mediaType.isConcrete()) {
- selectedMediaType = mediaType;
- break;
- }
- else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
- selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
- break;
- }
- }
- if (selectedMediaType != null) {
- selectedMediaType = selectedMediaType.removeQualityValue();
- for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
- if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
- ((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
- if (logger.isDebugEnabled()) {
- logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
- messageConverter + "]");
- }
- return;
- }
- }
- }
- throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
- }
選取一個合適的content-type,再由這個content-type和返回類型來選取合適的HttpMessageConverter,找到合適的HttpMessageConverter後,便調用它的write方法。
接下來就說一說一些具體的HttpMessageConverter。
AbstractHttpMessageConverter:提供了進一步的抽象,將是否支持相應的MediaType這一共有的功能實現,它的子類只需關心是否支持返回類型。
AbstractHttpMessageConverter子類-StringHttpMessageConverter:如用於處理字符串到response中,這就要涉及編碼問題,這一過程在本系列的第四篇文章中做過詳細說明,這裏跳過。
AbstractHttpMessageConverter子類-ByteArrayHttpMessageConverter:
- public class ByteArrayHttpMessageConverter extends AbstractHttpMessageConverter<byte[]> {
- /** Creates a new instance of the {@code ByteArrayHttpMessageConverter}. */
- public ByteArrayHttpMessageConverter() {
- super(new MediaType("application", "octet-stream"), MediaType.ALL);
- }
- @Override
- public boolean supports(Class<?> clazz) {
- return byte[].class.equals(clazz);
- }
- @Override
- public byte[] readInternal(Class<? extends byte[]> clazz, HttpInputMessage inputMessage) throws IOException {
- long contentLength = inputMessage.getHeaders().getContentLength();
- ByteArrayOutputStream bos =
- new ByteArrayOutputStream(contentLength >= 0 ? (int) contentLength : StreamUtils.BUFFER_SIZE);
- StreamUtils.copy(inputMessage.getBody(), bos);
- return bos.toByteArray();
- }
- @Override
- protected Long getContentLength(byte[] bytes, MediaType contentType) {
- return (long) bytes.length;
- }
- @Override
- protected void writeInternal(byte[] bytes, HttpOutputMessage outputMessage) throws IOException {
- StreamUtils.copy(bytes, outputMessage.getBody());
- }
- }
源碼就很清晰明瞭。它專門負責byte[]類型的轉換。
AbstractHttpMessageConverter子類-MappingJacksonHttpMessageConverter:用於轉換Object到json字符串類型。已過時,使用的是http://jackson.codehaus.org中Jackson 1.x的ObjectMapper,取代者爲MappingJackson2HttpMessageConverter。依賴爲:
- <dependency>
- <groupId>org.codehaus.jackson</groupId>
- <artifactId>jackson-core-asl</artifactId>
- <version>1.9.11</version>
- </dependency>
- <dependency>
- <groupId>org.codehaus.jackson</groupId>
- <artifactId>jackson-mapper-asl</artifactId>
- <version>1.9.11</version>
- </dependency>
AbstractHttpMessageConverter子類-MappingJackson2HttpMessageConverter:
它所使用的json轉換器是http://jackson.codehaus.org中Jackson 2.x的ObjectMapper。
依賴的jar包爲有3個,jackson-databind和它的兩個依賴jackson-annotations、jackson-core,但是有了jackson-databind的pom文件會去自動下載它的依賴,所以只需增添jackson-databind的pom即可獲取上述3個jar包:
- <dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- <version>2.4.2</version>
- </dependency>
接下來便說道:在註冊HttpMessageConverter過程中的一些問題:
- if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
- messageConverters.setSource(source);
- messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));
- RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
- stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
- messageConverters.add(stringConverterDef);
- messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
- messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
- messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));
- if (romePresent) {
- messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
- messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
- }
- if (jaxb2Present) {
- messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
- }
- if (jackson2Present) {
- messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));
- }
- else if (jacksonPresent) {
- messageConverters.add(createConverterDefinition(
- org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.class, source));
- }
- }
這段代碼是在註冊默認的HttpMessageConverter,但是個別HttpMessageConverter也是有條件的。即相應的jar包存在,纔會去註冊它。如MappingJackson2HttpMessageConverter,if (jackson2Present) {
messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));當jackson2Present爲true時纔會註冊。而jackson2Present的值如下:
- private static final boolean jackson2Present =
- ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
- ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
也就是當com.fasterxml.jackson.databind.ObjectMapper和com.fasterxml.jackson.core.JsonGenerator存在在classpath中才會去加載MappingJackson2HttpMessageConverter。
同理,MappingJacksonHttpMessageConverter的判斷如下:
- private static final boolean jacksonPresent =
- ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
- ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
所以當我們程序沒法轉換json時,你就需要考慮是否已經把MappingJacksonHttpMessageConverter或者MappingJackson2HttpMessageConverter的依賴加進來了,官方推薦使用MappingJackson2HttpMessageConverter。
轉載自:http://blog.csdn.net/z69183787/article/details/52817067