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處理中文亂碼問題 List<MediaType> fastMediaTypes = 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<HttpMessageConverter<?>> 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<MediaType> 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<HttpMessageConverter<?>> 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<HttpMessageConverter<?>> 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<T> { boolean canRead(Class<?> clazz, MediaType mediaType); boolean canWrite(Class<?> clazz, MediaType mediaType); 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; }
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<DemoObj> { 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<Object> { public static final ObjectMapper mapper = new ObjectMapper(); public static final Logger LOG = LoggerFactory.getLogger(MappingJackson2HttpMessageConverter.class); private boolean encryptFlag = false; public void setEncryptFlag(boolean encryptFlag) { this.encryptFlag = encryptFlag; } public MappingJackson2HttpMessageConverter() { super(new MediaType("application", "json", Charset.forName("UTF-8"))); } protected boolean supports(Class<?> clazz) { return true; } protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return mapper.readValue(inputMessage.getBody(), clazz); } protected void writeInternal(Object d, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { byte[] jsonBytes; if(d.getClass()!=HuishiUserLoginResponse.class) { BasicRes r = new BasicRes(); if (d != null && d != SysConstant.NULL_RESPONSE) { LOG.info("====>>>>> 響應數據:response={}", d.toString()); if (encryptFlag) { try { long repStart = System.currentTimeMillis(); String json = ToJson(d); String data = CoderUtil.encryptAES(json); r.setData(data); long repEnd = System.currentTimeMillis(); long repTime = repEnd - repStart; logger.info("加密耗時==>" + repTime + " ms"); } catch (Exception e) { e.printStackTrace(); } } else { r.setData(d); } } jsonBytes = ToJsonAsBytes(r); }else{ jsonBytes = ToJsonAsBytes(d); } OutputStream out = outputMessage.getBody(); out.write(jsonBytes, 0, jsonBytes.length); out.close(); } public static byte[] ToJsonAsBytes(Object value) { try { return mapper.writeValueAsBytes(value); } catch (Exception var2) { LOG.error("ToJsonAsBytes error,Object:{}", value, var2); return null; } } public static String ToJson(Object value) { try { return mapper.writeValueAsString(value); } catch (Exception var2) { LOG.error("ToJosn error,Object:{}", value, var2); return ""; } } }
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