1.简介:
Spring在处理请求时,由合适的消息转换器将请求报文绑定为方法中的形参对象,在这里,同一个对象就有可能出现多种不同的消息形式,比如json和xml。同样,当响应请求时,方法的返回值也同样可能被返回为不同的消息形式,比如json和xml。
在Spring中,针对不同的消息形式,我们有不同的HttpMessageConverter实现类来处理各种消息形式。但是,只要这些消息所蕴含的“有效信息”是一致的,那么各种不同的消息转换器,都会生成同样的转换结果。至于各种消息间解析细节的不同,就被屏蔽在不同的HttpMessageConverter实现类中了。
2.应用:
方法一:
SpringBoot中很多配置都使用默认的,但是如果你自己手动配置了,那么容器就是使用你自己的配置
自定义消息转化器,只需要在@Configuration的类中添加消息转化器的@bean加入到Spring容器,就会被Spring Boot自动加入到容器中。
@Configuration public class FastJsonHttpMessageConverterConfig { @Bean public HttpMessageConverters fastJsonHttpMessageConverters(){ //1.需要定义一个convert转换消息的对象; FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); //2:添加fastJson的配置信息; FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); //3处理中文乱码问题 ListfastMediaTypes = new ArrayList<>(); fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); //4.在convert中添加配置信息. fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes); fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig); HttpMessageConverter> converter = fastJsonHttpMessageConverter; return new HttpMessageConverters(converter); } }
方法二:
在继承WebMvcConfigurerAdapter的类中重写(覆盖)configureMessageConverters方法
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import java.util.List; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; /** * Created by qhong on 2018/6/1 10:59 **/ @ControllerAdvice @Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configureMessageConverters(List> converters) { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); builder.serializationInclusion(JsonInclude.Include.NON_NULL); ObjectMapper objectMapper = builder.build(); SimpleModule simpleModule = new SimpleModule(); simpleModule.addSerializer(Long.class, ToStringSerializer.instance); objectMapper.registerModule(simpleModule); objectMapper.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true);// 忽略 transient 修饰的属性 converters.add(new MappingJackson2HttpMessageConverter(objectMapper)); //字符串转换器 //StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8")); //converters.add(converter); //FastJson转换器 // //1.需要定义一个convert转换消息的对象; // FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); // //2.添加fastJson的配置信息,比如:是否要格式化返回的json数据; // FastJsonConfig fastJsonConfig = new FastJsonConfig(); // fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); // //3处理中文乱码问题 // List fastMediaTypes = new ArrayList<>(); // fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); // //4.在convert中添加配置信息. // fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes); // fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig); // //5.将convert添加到converters当中. // converters.add(fastJsonHttpMessageConverter); super.configureMessageConverters(converters); } @Override public void extendMessageConverters(List> converters) { System.out.println("Converters:Begin"); System.out.println("num:"+converters.size()); for (HttpMessageConverter> messageConverter : converters) { System.out.println(messageConverter); } System.out.println("Converters:End"); } }
方法三:
使用extendMessageConverters方法,其实在上一个code中已经贴出来,只是用来查看总共有那些消息转换器的,通过上面的,可以查看的自己手动添加的消息转换器。
@Override public void extendMessageConverters(List> converters) { converters.clear(); converters.add(new FastJsonHttpMessageConverter()); }
3.原理
我们知道,Http请求和响应报文本质上都是一串字符串,当请求报文来到java世界,它会被封装成为一个ServletInputStream的输入流,供我们读取报文。响应报文则是通过一个ServletOutputStream的输出流,来输出响应报文。
我们从流中,只能读取到原始的字符串报文,同样,我们往输出流中,也只能写原始的字符。而在java世界中,处理业务逻辑,都是以一个个有业务意义的对象为处理维度的,那么在报文到达SpringMVC和从SpringMVC出去,都存在一个字符串到java对象的阻抗问题。这一过程,不可能由开发者手工转换。我们知道,在Struts2中,采用了OGNL来应对这个问题,而在SpringMVC中,它是HttpMessageConverter机制。
package org.springframework.http.converter; import java.io.IOException; import java.util.List; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; public interface HttpMessageConverter{ boolean canRead(Class> clazz, MediaType mediaType); boolean canWrite(Class> clazz, MediaType mediaType); List getSupportedMediaTypes(); T read(Class extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
HttpMessageConverter接口的定义出现了成对的canRead(),read()和canWrite(),write()方法,MediaType是对请求的Media Type属性的封装。举个例子,当我们声明了下面这个处理方法。
@RequestMapping(value="/query", method=RequestMethod.POST) public @ResponseBody User queryUser(@RequestBody String tel) { return query(tel); }
在SpringMVC进入queryUser方法前,会根据@RequestBody注解选择适当的HttpMessageConverter实现类来将请求参数解析到string变量中,具体来说是使用了StringHttpMessageConverter类,它的canRead()方法返回true,然后它的read()方法会从请求中读出请求参数,绑定到readString()方法的string变量中。
当SpringMVC执行readString方法后,由于返回值标识了@ResponseBody,SpringMVC将使用MappingJackson2HttpMessageConverter(或者自定义的FastJsonHttpMessageConverter)的write()方法,将结果转换成json字符串写入响应报文,当然,此时canWrite()方法返回true。
我们可以用下面的图,简单描述一下这个过程。
上面只是利用一些第三方的消息转换器
自定义消息转换器:
捡到网上一个注释比较全的。
public class MyMessageConverter extends AbstractHttpMessageConverter{ public MyMessageConverter() { //x-zyf 是自定义的媒体类型 super(new MediaType("application", "x-zyf", Charset.forName("Utf-8"))); } @Override protected boolean supports(Class> aClass) { //表示只支持DemoObj这个类 //return DemoObj.class.isAssignableFrom(aClass); //返回false则不会支持任何类,要想使用,就需要返回true return true; } /** * 重写readInternal方法 * 处理请求中的数据 * * @param aClass * @param httpInputMessage * @return * @throws IOException * @throws HttpMessageNotReadableException */ @Override protected DemoObj readInternal(Class extends DemoObj> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException { //获得请求中的数据,得到字符串形式 String temp = StreamUtils.copyToString(httpInputMessage.getBody(), Charset.forName("UTF-8")); //前端请求的格式是我们自己约定的 String[] tempArr = temp.split("-"); return new DemoObj(new Long(tempArr[0]), tempArr[1]); } /** * 重写writeInternal方法 * 处理任何输出数据到response * * @param obj 要输出到response的对象 * @param httpOutputMessage * @throws IOException * @throws HttpMessageNotWritableException */ @Override protected void writeInternal(DemoObj obj, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException { String out = "hello:" + obj.getId() + "-" + obj.getName(); httpOutputMessage.getBody().write(out.getBytes()); } }
这里特别要注意的就是support,特么的,我以为返回false就是不进行消息转换呢,原来不是。
readInternal:
public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter
https://www.jianshu.com/p/ffe56d9553fd
https://www.cnblogs.com/hellxz/p/8735602.html
https://blog.csdn.net/mickjoust/article/details/51671060
https://www.cnblogs.com/page12/p/8166935.html
https://my.oschina.net/lichhao/blog/172562
https://blog.csdn.net/L_Sail/article/details/70209845
https://blog.csdn.net/linFeng_csdn/article/details/72835451