spring boot json 动态过滤属性 自定义标签 完美解决 兼容原生 @ResponseBody @JsonIgnoreProperties @JsonView

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> 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框架中。

你可能感兴趣的:(spring,boot)