在实际的开发过程中,会经常遇到如下情景:
本文通过自定义注解的方式进行实现,对response进行拦截,通过注解参数,设置字段信息(即,过滤哪些字段,保留哪些字段),并将bean自动封装为json,作为结果返回。
实现的具体代码可以在github中直接下载运行:https://github.com/MonkeyJJC/JsonFilter
整体思路:
(1)通过ResponseBodyAdvice实现在响应体写出之前做一些处理;比如,修改返回值、加密等(此处即进行参数解析,设置过滤器的相关参数,为后续消息转换器处理准备)
(2)自定义消息转换器HttpMessageConverter
(3)自定义消息转换器的配置,通过@Bean定义HttpMessageConverter是向项目中添加消息转换器,如果Spring扫描到HttpMessageConverter类型的bean,就会将它自动添加到调用链中。否则spring会使用默认的处理器
@RestController
public class Demo {
@GetMapping("user")
@SerializeField(clazz = User.class, includes = {"name", "id"})
public User user() {
User user = new User(1L, "jjc", "123456");
return user;
}
}
其中@SerializeField就是自定义的注解,通过注解的方式声明是对User类进行过滤操作,留下的字段是{“name”, “id”},先看一下实现效果:
{
id: 1,
name: "jjc"
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SerializeField {
Class clazz();
/**
* 需要返回的字段
* @return
*/
String[] includes() default {};
/**
* 需要去除的字段
* @return
*/
String[] excludes() default {};
}
对通过@ResponseBody返回的结果进行拦截(返回的结果即beforeBodyWrite中的Object,此处进行拦截,并进行相关处理)
@ControllerAdvice
public class JsonFilterResponseBodyAdvice implements ResponseBodyAdvice {
·····
@Override
public Object beforeBodyWrite(Object object, MethodParameter methodParameter, MediaType mediaType, Class converterType, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
JsonFilterObject jsonFilterObject = new JsonFilterObject();
if (null == object) {
return null;
}
if (!methodParameter.getMethod().isAnnotationPresent(SerializeField.class)) {
return object;
}
/**
* 处理类进行过滤处理
*/
if (methodParameter.getMethod().isAnnotationPresent(SerializeField.class)) {
/**
* java.lang.reflect.Method.getAnnotation(Class annotationClass)
* @return: 如果存在于此元素,则返回该元素注释指定的注释类型,否则返回为null
* 在处理类中,会对应强转为对应的类型,如SerializeField类
*/
Object obj = methodParameter.getMethod().getAnnotation(SerializeField.class);
handleAnnotation(SerializeField.class, obj, jsonFilterObject);
}
/**
* 不进行set,返回null,因为未初始化
*/
jsonFilterObject.setObject(object);
return jsonFilterObject;
}
private void handleAnnotation(Class clazz, Object object, JsonFilterObject jsonFilterObject) {
/**
* 获取注解使用处的参数信息,如@SerializeField(clazz = Address.class,includes = {"school", "home", "user"})
* 获取clazz类型及includes等信息
*/
}
可以看到,springMVC通过消息转换器,进行最后的输出,通过ResponseBodyAdvice拦截之后,解析到相关信息,初始化了过滤器,在此处,自定义的消息转换器中,调用过滤器,进行过滤相关操作:
public class JsonFilterHttpMessageConverter extends FastJsonHttpMessageConverter {
private Charset charset;
private SerializerFeature[] features;
public JsonFilterHttpMessageConverter() {
super();
setSupportedMediaTypes(Arrays.asList(
new MediaType("application", "json", UTF8),
new MediaType("application", "*+json", UTF8),
new MediaType("application", "jsonp", UTF8),
new MediaType("application", "*+jsonp", UTF8)));
setCharset(UTF8);
setFeatures(SerializerFeature.DisableCircularReferenceDetect,SerializerFeature.WriteMapNullValue);
}
/**
*
* @param obj- the object to write to the output message
* @param outputMessage- the HTTP output message to write to
* @throws IOException- in case of I/O errors
* @throws HttpMessageNotWritableException- in case of conversion errors
* 官方地址:https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/converter/AbstractGenericHttpMessageConverter.html#writeInternal-T-org.springframework.http.HttpOutputMessage-
*/
@Override
protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
if(obj instanceof JsonFilterObject){
JsonFilterObject jsonFilterObject = (JsonFilterObject) obj;
OutputStream out = outputMessage.getBody();
SimpleSerializerFilter simpleSerializerFilter = new SimpleSerializerFilter(jsonFilterObject.getIncludes(), jsonFilterObject.getExcludes());
/**
* JSON序列化接口toJSONString
* String toJSONString(Object, SerializeFilter, SerializerFeature...)
*/
String text = JSON.toJSONString(jsonFilterObject.getObject(), simpleSerializerFilter, features);
byte[] bytes = text.getBytes(this.charset);
out.write(bytes);
}else {
/**
* 未声明@SerializeField注解
*/
OutputStream out = outputMessage.getBody();
String text = JSON.toJSONString(obj, this.features);
byte[] bytes = text.getBytes(this.charset);
out.write(bytes);
}
}
@Override
public void setCharset(Charset charset) {
this.charset = charset;
}
@Override
public void setFeatures(SerializerFeature... features) {
this.features = features;
}
}
(嵌套类字段的筛选)
@GetMapping("userMulti") @SerializeField(clazz = User.class, includes = {"name", "id", "addresses"}) /** * 进行二次过滤,如使用相同注解SerializeField,Advice处只会判定执行一次 */ @MultiSerializeField(clazz = Address.class, excludes = {"user"}) public User userMulti() { User user = new User(1L, "jjc", "123456"); List
addresses = new ArrayList<>(); Address a1 = new Address("liuyis's home", "liuyis's school", user); Address a2 = new Address("liuyis's home2", "liuyis's school2", user); addresses.add(a1); addresses.add(a2); user.setAddresses(addresses); return user; }
结果输出:
{
addresses: [
{
home: "liuyis's home",
school: "liuyis's school"
},
{
home: "liuyis's home2",
school: "liuyis's school2"
}
],
id: 1,
name: "jjc"
}
完整代码可以从github中直接下载运行:https://github.com/MonkeyJJC/JsonFilter
对于复杂bean的过滤,如bean中字段包含容器或其他bean的情况,后续会对轮子进行相关功能完善。
======2018.11.28功能完善:接口响应体格式统一封装
结果返回形式:
{
code: 200,
data: {
id: 2,
name: "JJC"
},
msg: "success"
}
统一处理下接口的返回格式问题,将返回结果封装到“规范模板”中
对于spring消息转换器等概念可以参考:
SpringMVC源码剖析(五)-消息转换器HttpMessageConverter
Spring Boot:定制HTTP消息转换器
Spring MVC返回值处理踩坑笔记