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。

SpringBoot 消息转换器 HttpMessageConverter插图

我们可以用下面的图,简单描述一下这个过程。

上面只是利用一些第三方的消息转换器

自定义消息转换器:

捡到网上一个注释比较全的。

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 "";
        }
    }
}

SpringBoot技术视频

CSDN学院:《Spring Boot 视频教程全家桶》



SpringBoot 消息转换器 HttpMessageConverter插图1

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:http://www.choupangxia.com/2020/01/07/springboot-httpmessageconverter/