• SpringBoot 消息转换器 HttpMessageConverter


    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

    https://blog.csdn.net/L_Sail/article/details/70209845

    https://blog.csdn.net/linFeng_csdn/article/details/72835451

  • 相关阅读:
    C语言(1)
    ​ Markdown
    多功能嵌入式解码软件(4)
    多功能嵌入式解码软件(3)
    多功能嵌入式解码软件(2)
    STM32最小系统设计
    C#通过字符串分割字符串Split
    基于串口的SD_card系统
    直流无刷电机工作原理
    Java常用函数式接口--Consumer接口使用案例
  • 原文地址:https://www.cnblogs.com/hongdada/p/9120899.html
Copyright © 2020-2023  润新知