在介绍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); }
如果有上述的这些需求,那你需要了解一下 HttpMessageConverter接口,和之前文章提到的 HandlerMethodReturnValueHandler 和 ResponseBodyAdvice 功能类似,这是第三个能处理返回值的接口了。相比于前面两个接口,前面两个都太底层了,HttpMessageConverter更像是框架留给用户的接口,它在系统中不是唯一存在的,用户可以指定多个HttpMessageConverter,并且设置不同的优先级。
回归上述的需求,需要给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 + ""}"; } }