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初体验
小D课堂 - 零基础入门SpringBoot2.X到实战_汇总
springboot2.3.7升级到springboot2.7.2
2019刘老师教你用springboot2.x开发整合微信支付的线上教育平台带源码送springboot2.x零基础入门到高级实战教程