3.盤點springmvc的經常使用接口之HttpMessageConverter

3. 盤點springmvc的經常使用接口之HttpMessageConverter###

前言

舉例: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爲實體類型。

示例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,只是當參數HttpOutputMessageStreamingHttpOutputMessage時另行處理了。

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();
  }
}

友情連接:

盤點springmvc的經常使用接口目錄

相關文章
相關標籤/搜索