spring boot json 属性动态过滤 完美解决 兼容原生 @ResponseBody @JsonIgnoreProperties @JsonView
先说说环境是spring boot 2.1x版本 其他版本都是一样。
说说关键点在哪,主要是com.fasterxml.jackson.databind.ObjectMapper这个类在spring框架里是单独的,所以我们要想法把动态过滤的时候做一个拷贝出来,但是尽量不要破坏spring的程序流程和结构。
第一步 我们先要截取Controller定义的注解,这个类只需要被spring扫描到就可以执行
@ControllerAdvice
public class JFilterResponseBodyAdvice extends AbstractMappingJacksonResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType) {
return super.supports(returnType, converterType) && ( returnType.hasMethodAnnotation(JFilters.class) || returnType.hasMethodAnnotation(JFilter.class) );
}
protected MappingJacksonValue getOrCreateContainer(Object body) {
return (body instanceof MappingJacksonValue ? (MappingJacksonValue) body : new JFilterValue(body));
}
@Override
protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,
MethodParameter returnType, ServerHttpRequest request,
ServerHttpResponse response) {
if (returnType.hasMethodAnnotation(JFilters.class)) {
((JFilterValue) bodyContainer).setJfilters(returnType.getMethodAnnotation(JFilters.class).value());
} else if (returnType.hasMethodAnnotation(JFilter.class)) {
((JFilterValue) bodyContainer).setJfilters(
new JFilter[]{returnType.getMethodAnnotation(JFilter.class)}
);
}
}
}
第二步 我们需要把接受到的过滤信息存储到MappingJacksonValue bodyContainer里,在spring jaskson里是通过MappingJacksonValue进行信息传输的,所以我们要继承他添加我们需要的属性
public class JFilterValue extends MappingJacksonValue {
private JFilter[] jfilters = null;
/**
* Create a new instance wrapping the given POJO to be serialized.
*
* @param value the Object to be serialized
*/
public JFilterValue(Object value) {
super(value);
}
public JFilter[] getJfilters() {
return jfilters;
}
public void setJfilters(JFilter[] jfilters) {
this.jfilters = jfilters;
}
}
解析一下JFilterResponseBodyAdvice 的逻辑很简单,但有自定义标签就用JFilterValue,没有的话就用原生的MappingJacksonValue,JFilter[] jfilters 就是我们存储的过滤信息,然后在后边代码里调用。
第三步 也是最重要的一步在jaskson输出前我们要拷贝一份ObjectMapper出来在运行其他相关代码
public class JFilterHttpMessageConverter extends MappingJackson2HttpMessageConverter {
public PrettyPrinter mSsePrettyPrinter;
public JFilterHttpMessageConverter() {
super();
}
public JFilterHttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper);
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
this.mSsePrettyPrinter = prettyPrinter;
}
@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
//这里要判断是否是自己定义的存储类,如果是就是说明有自定义标签
if (object instanceof JFilterValue) {
//用单独objectMapper处理
ObjectMapper objectMapper = this.objectMapper.copy();
BeanJFilter.setJFilter((JFilterValue) object, objectMapper);
//以下代码跟源代码一样
MediaType contentType = outputMessage.getHeaders().getContentType();
JsonEncoding encoding = getJsonEncoding(contentType);
JsonGenerator generator = objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
try {
writePrefix(generator, object);
Object value = object;
Class> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;
if (object instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) object;
value = container.getValue();
serializationView = container.getSerializationView();
filters = container.getFilters();
}
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
javaType = getJavaType(type, null);
}
ObjectWriter objectWriter = (serializationView != null ?
objectMapper.writerWithView(serializationView) : objectMapper.writer());
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && javaType.isContainerType()) {
objectWriter = objectWriter.forType(javaType);
}
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.mSsePrettyPrinter);
}
objectWriter.writeValue(generator, value);
writeSuffix(generator, object);
generator.flush();
} catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
} catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
}
} else {
//如果没有自定义标签就按原生的代码走
super.writeInternal(object, type, outputMessage);
}
}
}
以上代码比较多,其实不用管这么多,找到AbstractJackson2HttpMessageConverter的writeInternal方法直接复制出来,先把自定义的JFilterValue判断了,不是的走原生路线。然后把objectMapper 拷贝一份出来, ObjectMapper objectMapper = this.objectMapper.copy();,把所有引用到的objectMapper ,改成你COPY的objectMapper。就是只需要需改这个方法就可以。
第四步 做一个过滤器,用来过滤json属性,这里我只做了去除字段
@com.fasterxml.jackson.annotation.JsonFilter("BeanJFilter")
public class BeanJFilter extends SimpleBeanPropertyFilter implements Serializable {
public Map> includesClazz ;
public BeanJFilter() {
this.includesClazz = new HashMap<>();
}
public BeanJFilter addFilter(Class clazz, String propertyArray) {
includesClazz.put(clazz, new HashSet<>(Arrays.asList(propertyArray.split(","))));
return this;
}
protected boolean include(BeanPropertyWriter writer) {
if( includesClazz.containsKey(writer.getMember().getDeclaringClass()) ){
return !this.includesClazz.get(writer.getMember().getDeclaringClass()).contains(writer.getName());
}
return true;
}
protected boolean include(PropertyWriter writer) {
if( includesClazz.containsKey(writer.getMember().getDeclaringClass()) ){
return !this.includesClazz.get(writer.getMember().getDeclaringClass()).contains(writer.getName());
}
return true;
}
public static void setJFilter(JFilterValue jFilterValue, ObjectMapper objectMapper ){
JFilter[] jFilter = jFilterValue.getJfilters();
if (jFilter.length > 0) {
BeanJFilter beanJFilter = new BeanJFilter();
for (int i = 0; i < jFilter.length; i++) {
beanJFilter.addFilter(jFilter[i].clazz(), jFilter[i].property());
objectMapper.addMixIn(jFilter[i].clazz(), BeanJFilter.class);
}
objectMapper.setFilterProvider(new SimpleFilterProvider().addFilter("BeanJFilter", beanJFilter));
}
}
}
JFilterHttpMessageConverter 中代码段中 BeanJFilter.setJFilter((JFilterValue) object, objectMapper);
就是加入过滤规则。
JFilterHttpMessageConverter 需要在springboot中定义bean如下
@Order(0)
@Bean
public JFilterHttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
JFilterHttpMessageConverter messageConverter = new JFilterHttpMessageConverter(objectMapper);
return messageConverter;
}
这样完事了。本人测试已经可以完美的融合到spring框架中。