3.盘点springmvc的常用接口之HttpMessageConverter

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,只是当参数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的常用接口目录

转载于:https://my.oschina.net/sugarZone/blog/704583

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值