SpringBoot2(十三)HttpMessageConverter

Posted 疯狂的妞妞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot2(十三)HttpMessageConverter相关的知识,希望对你有一定的参考价值。

注意:本文的代码设计非常的另类,这样的设计,在公司内部或者自己的团队使用,能节省大量的代码。

但是,Jackson和FastJSON提供了HttpMessageConverter接口的实现类,这些实现类往往成为了企业开发的标准接口,

项目中如果集成了其它公司的产品,我们的HttpMessageConverter有可能顺带地改掉他们的数据格式,因此,我不推荐自定义HttpMessageConverter

HandlerMethodReturnValueHandler 和  ResponseBodyAdvice 两个接口,可以通过包名进行过滤,可以只对部分代码生效,更适合完成我下列提出的需求。

 

Controller的返回值,先经过HandlerMethodReturnValueHandler 和  ResponseBodyAdvice 的处理,之后再由HttpMessageConverter进行处理,它在系统中不是唯一存在的,用户可以指定多个HttpMessageConverter,可以针对不同的数据类型(Mime类型),做不同的数据格式转换。

 

在介绍HttpMessageConverter之前,先看看平时常常写的一些代码

1、在JDBC的操作中,执行Update的时候,会返回受影响行数,你是如何处理的? 

  /**
     * 搞笑的写法
     */
    public MResult eg() {
        int rows = service.doUpdate();
        if(rows > 0){
            return new MResult(MResult.ERROR);
        } else {
            return new MResult(MResult.SUCCESS);
        }
    }

  /**
     * 稍作优化,但是还不够
     */
    public MResult eg() {
        int rows = service.doUpdate();
        return MResult.doUpdate(rows);
    }

  /**
     * 希望Controller也可以这样写,那该如何去处理切面?
     */
    public int eg() {
        return service.doUpdate();
    }

2、在做数据查询的时候,你是否希望直接返回数据,系统自动帮你打包数据?

  /**
     * 搞笑的写法
     */
    @ResponseBody
    @RequestMapping(value = "/find")
    public TAosPlaneChkEntity findById(String id){
        Object res = service.findById(id);
        return res == null?new MResult(MResult.ERROR):new MResult(MResult.SUCCESS, res);
    }

  /**
     * 因此,希望Controller这样写,数据自动装箱
     */
    @ResponseBody
    @RequestMapping(value = "/find")
    public TAosPlaneChkEntity findById(String id){
        return service.findById(id);
    }

 

回归上述的需求,需要给Controller立下我们的数据规则:

1、当返回值为int时,一律当成受影响行数处理,
  如果大于0,则返回{"code":"0","data":"操作成功!"}
  如果等于0,则返回{"code":"1","data":"操作失败!"}

2、当返回值为Object时,则对数据进行包装:
  变为{"code":"1","data":{"name":"xiaoming","age":"30"}}格式

3、当返回值为String时,则对数据进行原样返回

异常状态有专门切面可以处理,不纳入思考范围。

GenericHttpMessageConverter

代码改编自com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter,此接口负责 Controller 的数据包装和写入。

import cn.seaboot.common.core.FastJsonUtils;
import cn.seaboot.plugin.util.Result;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import org.springframework.http.HttpHeaders;
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.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;

/**
 * 改造FastJson,所有返回值进行二次封装
 * @author  Mr.css
 * @date    2018年7月15日  上午12:11:14  v1
 *          2019年10月16日 上午11:08     v2
 */
public class FastJsonConverter extends AbstractHttpMessageConverter<Object> implements GenericHttpMessageConverter<Object> {
    private FastJsonConfig fastJsonConfig;

    public FastJsonConverter() {
        super(MediaType.ALL);
        this.fastJsonConfig = new FastJsonConfig();
        this.fastJsonConfig.setCharset(Charset.defaultCharset());
        //下列代码未给出,参考FastJson配置
        this.fastJsonConfig.setSerializeConfig(FastJsonUtils.serializeConfig);
        this.fastJsonConfig.setSerializerFeatures(FastJsonUtils.features);
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        InputStream in = inputMessage.getBody();
        return JSON.parseObject(in, this.fastJsonConfig.getCharset(), clazz, this.fastJsonConfig.getFeatures());
    }

    /**
     * 重点改造这一函数
     */
    @Override
    protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        HttpHeaders headers = outputMessage.getHeaders();
        ByteArrayOutputStream outnew = new ByteArrayOutputStream();

        OutputStream out;
        String text;
        if (obj instanceof Integer) {
            //Integer 类型处理
            text = Result.doUpdate((int) obj);
        } else if (obj instanceof String) {
            text = obj.toString();
        }  else {
            //自定义JSON格式
            text = Result.succeed(obj);
        }

        out = outputMessage.getBody();
        out.write(text.getBytes(this.fastJsonConfig.getCharset()));
        if(this.fastJsonConfig.isWriteContentLength()){
            headers.setContentLength((long) text.length());
        }
        outnew.close();
    }

    @Override
    public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
        return super.canRead(contextClass, mediaType);
    }

    @Override
    public boolean canWrite(Type type, Class<?> contextClass, MediaType mediaType) {
        return super.canWrite(contextClass, mediaType);
    }

    @Override
    public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        InputStream in = inputMessage.getBody();
        return JSON.parseObject(in, this.fastJsonConfig.getCharset(), type, this.fastJsonConfig.getFeatures());
    }

    @Override
    public void write(Object t, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        HttpHeaders headers = outputMessage.getHeaders();
        if (headers.getContentType() == null) {
            if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {
                contentType = this.getDefaultContentType(t);
            }

            if (contentType != null) {
                headers.setContentType(contentType);
            }
        }

        if (headers.getContentLength() == -1L) {
            Long contentLength = this.getContentLength(t, headers.getContentType());
            if (contentLength != null) {
                headers.setContentLength(contentLength.longValue());
            }
        }

        this.writeInternal(t, outputMessage);
        outputMessage.getBody().flush();
    }
}

 

Spring中的使用

因为是代码改造,配置与FastJSON的实现类完全相同

    <!-- 配置与FastJsonHttpMessageConverter完全相同 -->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="xxxxxxxxxxx.FastJsonConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>application/json;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

SpringBoot的使用

import cn.seaboot.common.core.Resource;
import cn.seaboot.plugin.config.ArgumentResolver;
import cn.seaboot.plugin.config.FastJsonConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 *
 * @author Created by 12614 on 2018/5/11.
 */
@Configuration
public class ApplicationConfigurer implements WebMvcConfigurer {
    private Logger logger = LoggerFactory.getLogger(ApplicationConfigurer.class);

    /**
     * JSON解析
     * @param converters -
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        //去除不需要的配置
        String[] exclude = {
                "org.springframework.http.converter.xml"
                , "org.springframework.http.converter.json"};
        Iterator<HttpMessageConverter<?>> ite = converters.iterator();
        while (ite.hasNext()){
            HttpMessageConverter<?> converter = ite.next();
            String name = converter.getClass().getName();
            for (String str: exclude) {
                if(name.startsWith(str)){
                    ite.remove();
                    break;
                }
            }
        }

        FastJsonConverter fastConverter = new FastJsonConverter();
        // 处理中文乱码问题
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        fastConverter.setSupportedMediaTypes(fastMediaTypes);
        // 提高优先级,放到集合开头
        converters.set(0, fastConverter);
    }
}

Result

对于Controller返回值的二次封装工具,按照自己需求设计,此处仅供参考。

import cn.seaboot.common.core.FastJsonUtils;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * 返回值封装
 * @author ChenSS
 * @date     2018-10-12 17:19      提交
 *               2019年10月15日 17:21   修订
 */
public class Result implements Serializable {
    public static final Map<String, Object> ERROR_MAP = new HashMap<>();
    public static final String EMPTY_JSON = "{}";

    private static final String SUCCEED = "{\\"code\\":\\"0\\",\\"data\\":\\"操作成功\\"}";
    private static final String FAILED = "{\\"code\\":\\"1\\",\\"data\\":\\"操作失败\\"}";

    public static final String NOT_LOGIN = "{\\"code\\":\\"5\\",\\"data\\":\\"用户未登录!\\"}";
    public static final String LACK_PERMISSION = "{\\"code\\":\\"4\\",\\"data\\":\\"你缺少权限来执行此操作!\\"}";
    public static final String NAME_OR_PWD_WRONG = "{\\"code\\":\\"6\\",\\"data\\":\\"用户名或者密码错误!\\"}";

    private String code;
    private Object data;

    static {
        ERROR_MAP.put("code", 1);
        ERROR_MAP.put("data", "未知的错误!请联系管理员!");
    }

    public Result(String code, Object data) {
        this.code = code;
        this.data = data;
    }

    public static String succeed(String data) {
        return "{\\"code\\":\\"0\\",\\"data\\":\\"" + data + "\\"}";
    }

    public static String succeed(Object data) {
        return "{\\"code\\":\\"0\\",\\"data\\":" + FastJsonUtils.toJSONString(data) + "}";
    }

    public static String failed(String data) {
        return "{\\"code\\":\\"1\\",\\"data\\":\\"" + data + "\\"}";
    }

    public static String failed(Object data) {
        return "{\\"code\\":\\"1\\",\\"data\\":" + FastJsonUtils.toJSONString(data) + "}";
    }

    public static String doUpdate(int row, String success, String fail) {
        return row > 0 ? Result.succeed(success) : Result.failed(fail);
    }

    public static String doUpdate(int row, String success) {
        return row > 0 ? Result.succeed(success) : Result.FAILED;
    }

    public static String doUpdate(int row) {
        return row > 0 ? Result.SUCCEED : Result.FAILED;
    }

    public static String succeed() {
        return SUCCEED;
    }

    public static String failed() {
        return FAILED;
    }

    public static Map<String, Object> build(String code, Object data) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", code);
        map.put("data", data);
        return map;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "{\\"code\\":\\"" + code + "\\",\\"data\\":\\"" + data + "\\"}";
    }
}

 

以上是关于SpringBoot2(十三)HttpMessageConverter的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot2.X最佳实践《一》 之 SpringBoot2.x初体验

2018最新SpringBoot2.0教程(零基础入门)

小D课堂 - 零基础入门SpringBoot2.X到实战_汇总

springboot2.3.7升级到springboot2.7.2

2019刘老师教你用springboot2.x开发整合微信支付的线上教育平台带源码送springboot2.x零基础入门到高级实战教程

SpringBoot2.0应用:SpringBoot2.0整合RabbitMQ