解决springmvcjson循环依赖问题:Spring MVC中使用jackson的MixInAnnotations方法动态过滤JSON字段

2015-10-14 blademainer 更多博文 » 博客 » GitHub »

java javassist spring-mvc jackson 循环

原文链接 https://blademainer.github.io/2015/10/14/2014-06-30-spring-mvc-jackson-enhance/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


问题描述

项目使用SpringMVC框架,并用jackson库处理JSON和POJO的转换。在POJO转化成JSON时,有些属性我们不需要输出或者有些属性循环引用会造成无法输出。

  • 例如:实体User其中包括用户名、密码、邮箱等,但是我们在输出用户信息不希望输出密码、邮箱信息;
  • 例如:实体user和department是多对一的关系,user内保存着department的信息,那么json输出时会导致这两个实体数据的循环输出;

jackson默认可以使用JsonIgnoreProperties接口来定义要过滤的属性,然后使用ObjectMapper#addMixInAnnotations来设置对应实体对应的JsonIgnoreProperties接口,这样就能达到过滤的目的。可是这样很不爽,因为如果你对n个实体对应有m种过滤需求就至少要建n*m个JsonIgnoreProperties接口。

<!--more-->

解决方案

主要逻辑如下图

大致处理流程:

  • 使用自定义注解controller方法
  • 然后定义aop捕获所有controller方法
  • 当aop捕获到controller方法时调用JavassistFilterPropertyHandler#filterProperties方法
  • filterProperties读取注解并根据自定义注解使用javassist创建JsonIgnoreProperties临时实现类(同时缓存到map内,下次可直接取出)并存入当前线程内(ThreadJacksonMixInHolder, 使用threadlocal实现),
  • 在springmvc输出json的类内自定义ObjectMapper, 从当前线程内取出JsonIgnoreProperties临时类, 调用ObjectMapper# addMixInAnnotations使之起效
  • 最后使用ObjectMapper输出

用法:

定义aop, 用来捕获springmvc的controller方法

package com.xiongyingqi.json.filter.aop;

import com.xiongyingqi.jackson.FilterPropertyHandler;
import com.xiongyingqi.jackson.impl.JavassistFilterPropertyHandler;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;

/**
 * @author 瑛琪 <a href="http://xiongyingqi.com">xiongyingqi.com</a>
 * @version 2013-9-27 下午5:41:12
 */
@Aspect
public class IgnorePropertyAspect {
    public static final Logger LOGGER = Logger.getLogger(IgnorePropertyAspect.class);

    @Pointcut("execution(* com.kingray.web.*.*(..))")
    private void anyMethod() {

    }

    @Around("anyMethod()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object returnVal = pjp.proceed(); // 返回源结果
        try {
            FilterPropertyHandler filterPropertyHandler = new JavassistFilterPropertyHandler(true);
            Method method = ((MethodSignature) pjp.getSignature()).getMethod();
            returnVal = filterPropertyHandler.filterProperties(method, returnVal);
        } catch (Exception e) {
            LOGGER.error(e);
            e.printStackTrace();
        }

        return returnVal;
    }

    @AfterThrowing(pointcut = "anyMethod()", throwing = "e")
    public void doAfterThrowing(Exception e) {
        System.out.println(" -------- AfterThrowing -------- ");
    }
}

spring配置

<!-- 启动mvc对aop的支持,使用aspectj代理 -->
<aop:aspectj-autoproxyproxy-target-class="true" />
<beanid="ignorePropertyAspect" class="com.xiongyingqi.json.filter.aop.IgnorePropertyAspect"></bean>

配置spring-mvc的messageconverter

<bean
        class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="cacheSeconds" value="0"/>
    <!--日期格式转换 -->
    <property name="webBindingInitializer">
        <bean class="com.kingray.spring.http.convert.DateConverter"/>
    </property>
    <property name="messageConverters">
        <list>
            <bean
                    class="com.xiongyingqi.spring.http.convert.json.Jackson2HttpMessageConverter">
            </bean>
            <bean
                    class="com.xiongyingqi.spring.http.convert.json.JacksonHttpMessageConverter">
            </bean>
            <bean
                    class="org.springframework.http.converter.BufferedImageHttpMessageConverter">
            </bean>
            <bean
                    class="org.springframework.http.converter.ResourceHttpMessageConverter">
            </bean>
            <bean class="org.springframework.http.converter.FormHttpMessageConverter">
            </bean>
            <bean
                    class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter">
            </bean>
            <bean
                    class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter">
            </bean>
            <bean
                    class="org.springframework.http.converter.feed.AtomFeedHttpMessageConverter">
            </bean>
            <bean
                    class="org.springframework.http.converter.feed.RssChannelHttpMessageConverter">
            </bean>
            <bean
                    class="org.springframework.http.converter.xml.Jaxb2CollectionHttpMessageConverter">
            </bean>
            <bean
                    class="org.springframework.http.converter.xml.SourceHttpMessageConverter">
            </bean>
            <bean
                    class="org.springframework.http.converter.ByteArrayHttpMessageConverter">
            </bean>
        </list>
    </property>

</bean>

重写spring的MappingJackson2HttpMessageConverter类,这样输出的json内容就能自定义


package com.xiongyingqi.spring.http.convert.json;

import java.io.IOException;

import com.xiongyingqi.jackson.helper.ThreadJacksonMixInHolder;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

/**
 * @author 瑛琪 <a href="http://xiongyingqi.com">xiongyingqi.com</a>
 * @version 2013-9-27 下午4:05:46
 */
public class Jackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
    private ObjectMapper objectMapper = new ObjectMapper();
    private boolean prefixJson = false;


    /**
     * <br>
     * 2013-9-27 下午4:10:28
     *
     * @see org.springframework.http.converter.json.MappingJacksonHttpMessageConverter#writeInternal(Object,
     * org.springframework.http.HttpOutputMessage)
     */
    @Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        // super.writeInternal(object, outputMessage);

        // 判断是否需要重写objectMapper
        ObjectMapper objectMapper = this.objectMapper;// 本地化ObjectMapper,防止方法级别的ObjectMapper改变全局ObjectMapper
        if (ThreadJacksonMixInHolder.isContainsMixIn()) {
            objectMapper = ThreadJacksonMixInHolder.builderMapper();
        }

        JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
        JsonGenerator jsonGenerator = objectMapper.getFactory().createGenerator(
                outputMessage.getBody(), encoding);

        // A workaround for JsonGenerators not applying serialization features
        // https://github.com/FasterXML/jackson-databind/issues/12
        if (objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
            jsonGenerator.useDefaultPrettyPrinter();
        }

        try {
            if (this.prefixJson) {
                jsonGenerator.writeRaw("{} && ");
            }
            objectMapper.writeValue(jsonGenerator, object);
        } catch (JsonProcessingException ex) {
            throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(),
                    ex);
        }
        // JsonEncoding encoding =
        // getJsonEncoding(outputMessage.getHeaders().getContentType());
        // JsonGenerator jsonGenerator =
        // this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(),
        // encoding);
        //
        // // A workaround for JsonGenerators not applying serialization
        // features
        // // https://github.com/FasterXML/jackson-databind/issues/12
        // if (this.objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT))
        // {
        // jsonGenerator.useDefaultPrettyPrinter();
        // }
        //
        // try {
        // if (this.prefixJson) {
        // jsonGenerator.writeRaw("{} && ");
        // }
        // this.objectMapper.writeValue(jsonGenerator, object);
        // }
        // catch (JsonProcessingException ex) {
        // throw new HttpMessageNotWritableException("Could not write JSON: " +
        // ex.getMessage(), ex);
        // }
    }

    public boolean isPrefixJson() {
        return prefixJson;
    }

    public void setPrefixJson(boolean prefixJson) {
        this.prefixJson = prefixJson;
    }

}

在方法上注解

Controller方法的示例,yxResourceSelfRelationsForSuperiorResourceId是YxResource内要过滤的属性:


    @IgnoreProperties(value= {
           @IgnoreProperty(pojo = YxResource.class, name = {
                  "yxResourceSelfRelationsForSuperiorResourceId"})})
   @RequestMapping(value = "/{resourceId}", method = RequestMethod.GET)
   @ResponseBody
   public Object getResourceByResourceId(@PathVariable Integer resourceId) {
       YxResource resource = resourceService.getResource(resourceId);
       return resource;
    }

主要类说明

自定义注解类:这些类是用于注解实体类输出json时要注解过滤的属性

IgnoreProperties.java 用于同时注解IgnorePropertyAllowProperty


package com.xiongyingqi.jackson.annotation;

import java.lang.annotation.*;

/**
 * json属性过滤注解,对于同一个pojo来说 @AllowProperty 是与 @IgnoreProperty 是冲突的,如果这两个注解注解了<br>
 * 例如以下代码YxResource实体只会显示resourceName和resourceDescribe属性
 * <p/>
 * <pre>
 * @IgnoreProperties(
 *     value = {
 *      @IgnoreProperty(
 *          pojo = YxResource.class,
 *          name = {
 *              "yxResourceDataRelations",
 *              "yxResourceSelfRelationsForSublevelResourceId",
 *              "yxPermisionResourceRelations" }),
 *      @IgnoreProperty(
 *          pojo = YxResourceSelfRelation.class,
 *          name = {
 *              "yxResourceBySuperiorResourceId",
 *              "id" })
 *    },
 *  allow = {
 *  @AllowProperty(
 *          pojo = YxResource.class,
 *          name = { "<b><i>resourceName</i></b>" }) })
 *  @AllowProperty(
 *          pojo = YxResource.class,
 *          name = { "<b><i>resourceDescribe</i></b>" })
 * </pre>
 * <p/>
 * <p/>
 * 但是,对于同一个pojo的同一属性来说@AllowProperty是与@IgnoreProperty则会按照@IgnoreProperty过滤的属性名过滤
 * 例如以下代码YxResource实体不会显示resourceName属性的值
 * <p/>
 * <pre>
 * @IgnoreProperties(
 *  value = {
 *  @IgnoreProperty(
 *          pojo = YxResource.class,
 *          name = { "<b><i>resourceName</i></b>",
 *              "yxResourceDataRelations",
 *              "yxResourceSelfRelationsForSublevelResourceId",
 *              "yxPermisionResourceRelations" }),
 *  @IgnoreProperty(
 *          pojo = YxResourceSelfRelation.class,
 *          name = {
 *              "yxResourceBySuperiorResourceId",
 *              "id" })
 *    },
 *  allow = {
 *  @AllowProperty(
 *          pojo = YxResource.class,
 *          name = { "<b><i>resourceName</i></b>" }) })
 * </pre>
 *
 * @author 瑛琪 <a href="http://xiongyingqi.com">xiongyingqi.com</a>
 * @version 2013-9-27 下午4:18:39
 */
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreProperties {
    /**
     * 要过滤的属性
     *
     * @return
     */
    IgnoreProperty[] value() default @IgnoreProperty(pojo = Object.class, name = "");

    /**
     * 允许的属性
     *
     * @return
     */
    AllowProperty[] allow() default @AllowProperty(pojo = Object.class, name = "");
}

IgnoreProperty.java:过滤指定对象内的指定字段名


package com.xiongyingqi.jackson.annotation;

import java.lang.annotation.*;

/**
 * 用于注解json过滤pojo内的属性,其他的属性都会被序列化成字符串
 *
 * @author 瑛琪 <a href="http://xiongyingqi.com">xiongyingqi.com</a>
 * @version 2013-9-27 下午4:24:33
 */
@Documented
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreProperty {
    /**
     * 要忽略字段的POJO <br>
     * 2013-9-27 下午4:27:08
     *
     * @return
     */
    Class<?> pojo();

    /**
     * 要忽略的字段名 <br>
     * 2013-9-27 下午4:27:12
     *
     * @return
     */
    String[] name();

    /**
     * 字段名,无论是哪种 <br>
     * 2013-9-27 下午4:27:15
     *
     * @return
     */
    //    String value() default "";

    /**
     * 最大迭代层次<br>
     * 当注解了pojo和name值时,该值表示遍历bean属性的最大曾次数,此注解一般用于自关联的bean类,
     * 如果循环层次大于等于maxLevel时则不再读取属性<br>
     * 如果maxIterationLevel为0,则不限制迭代层次<br>
     * 如果maxIterationLevel为1,则迭代读取属性一次<br>
     * 2013-10-21 下午2:16:26
     *
     * @return
     */
    //  int maxIterationLevel() default 0;
}

AllowProperty.java:注解实体类允许的字段


package com.xiongyingqi.jackson.annotation;

import java.lang.annotation.*;

/**
 * 只允许pojo内的属性序列化成json,对于同一个pojo该注解是与IgnoreProperty是冲突的<br>
 *
 * @author 瑛琪 <a href="http://xiongyingqi.com">xiongyingqi.com</a>
 * @version 2013-10-30 下午3:57:35
 */
@Documented
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AllowProperty {
    /**
     * 目标POJO <br>
     * 2013-9-27 下午4:27:08
     *
     * @return
     */
    Class<?> pojo();

    /**
     * 允许序列化的属性名 <br>
     * 2013-9-27 下午4:27:12
     *
     * @return
     */
    String[] name();
}

核心处理类,用于处理自定义注解并将生成的类存入当前线程

package com.xiongyingqi.jackson.impl;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xiongyingqi.jackson.FilterPropertyHandler;
import com.xiongyingqi.jackson.annotation.AllowProperty;
import com.xiongyingqi.jackson.annotation.IgnoreProperties;
import com.xiongyingqi.jackson.annotation.IgnoreProperty;
import com.xiongyingqi.jackson.helper.ThreadJacksonMixInHolder;
import com.xiongyingqi.util.EntityHelper;
import com.xiongyingqi.util.StringHelper;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.*;
import org.apache.log4j.Logger;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.ResponseBody;

import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.*;
import java.util.Map.Entry;

//@IgnoreProperty(pojo = YxUserRoleRelation.class, name = { "id", "yxUser" })

/**
 * 使用代理来创建jackson的MixInAnnotation注解接口<br>
 * 如果使用本实现方法,一定要配置在web.xml中配置过滤器WebContextFilter,否则无法输出json到客户端
 *
 * @author 瑛琪 <a href="http://xiongyingqi.com">xiongyingqi.com</a>
 * @version 2013-10-25 下午2:31:21
 */
public class JavassistFilterPropertyHandler implements FilterPropertyHandler {

    public static final Logger LOGGER = Logger.getLogger(JavassistFilterPropertyHandler.class);

    /**
     * 注解的方法对应生成的代理类映射表
     */
    private static Map<Method, Map<Class<?>, Class<?>>> proxyMethodMap = new HashMap<Method, Map<Class<?>, Class<?>>>();

    /**
     * String数组的hashCode与生成的对应的代理类的映射表
     */
    private static Map<Integer, Class<?>> proxyMixInAnnotationMap = new HashMap<Integer, Class<?>>();

    private static String[] globalIgnoreProperties = new String[]{"hibernateLazyInitializer",
            "handler" };

    /**
     * 如果是标注的SpringMVC中的Controller方法,则应判断是否注解了@ResponseBody
     */
    private boolean isResponseBodyAnnotation;
    /**
     * 创建代理接口的唯一值索引
     */
    private static int proxyIndex;

    public JavassistFilterPropertyHandler() {
    }

    public JavassistFilterPropertyHandler(String[] globalIgnoreProperties) {
        JavassistFilterPropertyHandler.globalIgnoreProperties = globalIgnoreProperties;
    }

    /**
     * @param isResponseBodyAnnotation 如果是标注的SpringMVC中的Controller方法,则应判断是否注解了@ResponseBody
     */
    public JavassistFilterPropertyHandler(boolean isResponseBodyAnnotation) {
        this.isResponseBodyAnnotation = isResponseBodyAnnotation;
    }

    /**
     * <br>
     * 2013-10-28 上午11:11:24
     *
     * @param collection
     * @param names
     * @return
     */
    private Collection<String> checkAndPutToCollection(Collection<String> collection, String[] names) {
        if (collection == null) {
            collection = new HashSet<String>();
        }
        Collections.addAll(collection, names);
        return collection;
    }

    private Collection<String> putGlobalIgnoreProperties(Collection<String> collection) {
        if (globalIgnoreProperties != null) {
            if (collection == null) {
                collection = new HashSet<String>();
            }
            for (int i = 0; i < globalIgnoreProperties.length; i++) {
                String name = globalIgnoreProperties[i];
                collection.add(name);
            }
        }
        return collection;
    }

    /**
     * 处理IgnoreProperties注解 <br>
     * 2013-10-30 下午6:15:41
     *
     * @param properties
     * @param pojoAndNamesMap
     */
    private void processIgnorePropertiesAnnotation(IgnoreProperties properties,
                                                   Map<Class<?>, Collection<String>> pojoAndNamesMap) {
        IgnoreProperty[] values = properties.value();

        AllowProperty[] allowProperties = properties.allow();

        if (allowProperties != null) {
            for (AllowProperty allowProperty : allowProperties) {
                processAllowPropertyAnnotation(allowProperty, pojoAndNamesMap);
            }
        }

        if (values != null) {
            for (IgnoreProperty property : values) {
                processIgnorePropertyAnnotation(property, pojoAndNamesMap);
            }
        }

    }

    /**
     * 处理IgnoreProperty注解 <br>
     * 2013-10-30 下午6:16:08
     *
     * @param property
     * @param pojoAndNamesMap
     */
    private void processIgnorePropertyAnnotation(IgnoreProperty property,
                                                 Map<Class<?>, Collection<String>> pojoAndNamesMap) {
        String[] names = property.name();
        Class<?> pojoClass = property.pojo();
        // Class<?> proxyAnnotationInterface = createMixInAnnotation(names);//
        // 根据注解创建代理接口

        Collection<String> nameCollection = pojoAndNamesMap.get(pojoClass);
        nameCollection = checkAndPutToCollection(nameCollection, names);
        pojoAndNamesMap.put(pojoClass, nameCollection);
    }

    /**
     * 处理AllowProperty注解 <br>
     * 2013-10-30 下午6:16:08
     *
     * @param property
     * @param pojoAndNamesMap
     */
    private void processAllowPropertyAnnotation(AllowProperty property,
                                                Map<Class<?>, Collection<String>> pojoAndNamesMap) {
        String[] allowNames = property.name();
        Class<?> pojoClass = property.pojo();

        Collection<String> ignoreProperties = EntityHelper
                .getUnstaticClassFieldNameCollection(pojoClass);

        Collection<String> allowNameCollection = new ArrayList<String>();
        Collections.addAll(allowNameCollection, allowNames);

        Collection<String> nameCollection = pojoAndNamesMap.get(pojoClass);
        if (nameCollection != null) {
            nameCollection.removeAll(allowNameCollection);
        } else {
            ignoreProperties.removeAll(allowNameCollection);
            nameCollection = ignoreProperties;
        }
        pojoAndNamesMap.put(pojoClass, nameCollection);
    }

    /**
     * 根据方法获取过滤映射表 <br>
     * 2013-10-25 下午2:47:34
     *
     * @param method 注解了 @IgnoreProperties 或 @IgnoreProperty 的方法(所在的类)
     * @return Map<Class<?>, Collection<Class<?>>> pojo与其属性的映射表
     */
    public Map<Class<?>, Class<?>> getProxyMixInAnnotation(Method method) {
        if (isResponseBodyAnnotation && !method.isAnnotationPresent(ResponseBody.class)) {
            return null;
        }
        Map<Class<?>, Class<?>> map = proxyMethodMap.get(method);// 从缓存中查找是否存在

        if (map != null && map.entrySet().size() > 0) {// 如果已经读取该方法的注解信息,则从缓存中读取
            return map;
        } else {
            map = new HashMap<Class<?>, Class<?>>();
        }

        Class<?> clazzOfMethodIn = method.getDeclaringClass();// 方法所在的class

        Map<Class<?>, Collection<String>> pojoAndNamesMap = new HashMap<Class<?>, Collection<String>>();

        IgnoreProperties classIgnoreProperties = clazzOfMethodIn
                .getAnnotation(IgnoreProperties.class);
        IgnoreProperty classIgnoreProperty = clazzOfMethodIn.getAnnotation(IgnoreProperty.class);
        AllowProperty classAllowProperty = clazzOfMethodIn.getAnnotation(AllowProperty.class);

        IgnoreProperties ignoreProperties = method.getAnnotation(IgnoreProperties.class);
        IgnoreProperty ignoreProperty = method.getAnnotation(IgnoreProperty.class);
        AllowProperty allowProperty = method.getAnnotation(AllowProperty.class);

        if (allowProperty != null) {// 方法上的AllowProperty注解
            processAllowPropertyAnnotation(allowProperty, pojoAndNamesMap);
        }
        if (classAllowProperty != null) {
            processAllowPropertyAnnotation(classAllowProperty, pojoAndNamesMap);
        }

        if (classIgnoreProperties != null) {// 类上的IgnoreProperties注解
            processIgnorePropertiesAnnotation(classIgnoreProperties, pojoAndNamesMap);
        }
        if (classIgnoreProperty != null) {// 类上的IgnoreProperty注解
            processIgnorePropertyAnnotation(classIgnoreProperty, pojoAndNamesMap);
        }

        if (ignoreProperties != null) {// 方法上的IgnoreProperties注解
            processIgnorePropertiesAnnotation(ignoreProperties, pojoAndNamesMap);
        }
        if (ignoreProperty != null) {// 方法上的IgnoreProperties注解
            processIgnorePropertyAnnotation(ignoreProperty, pojoAndNamesMap);
        }

        Set<Entry<Class<?>, Collection<String>>> entries = pojoAndNamesMap.entrySet();
        for (Iterator<Entry<Class<?>, Collection<String>>> iterator = entries.iterator(); iterator
                .hasNext(); ) {
            Entry<Class<?>, Collection<String>> entry = (Entry<Class<?>, Collection<String>>) iterator
                    .next();
            Collection<String> nameCollection = entry.getValue();
            nameCollection = putGlobalIgnoreProperties(nameCollection);// 将全局过滤字段放入集合内
            String[] names = nameCollection.toArray(new String[]{});

            // EntityHelper.print(entry.getKey());
            // for (int i = 0; i < names.length; i++) {
            // String name = names[i];
            // EntityHelper.print(name);
            // }
            Class<?> clazz = createMixInAnnotation(names);

            map.put(entry.getKey(), clazz);
        }

        proxyMethodMap.put(method, map);
        return map;
    }

    /**
     * 创建jackson的代理注解接口类 <br>
     * 2013-10-25 上午11:59:50
     *
     * @param names 要生成的字段
     * @return 代理接口类
     */
    private Class<?> createMixInAnnotation(String[] names) {
        Class<?> clazz = null;
        clazz = proxyMixInAnnotationMap.get(StringHelper.hashCodeOfStringArray(names));
        if (clazz != null) {
            return clazz;
        }

        ClassPool pool = ClassPool.getDefault();

        // 创建代理接口
        CtClass cc = pool.makeInterface("ProxyMixInAnnotation" + System.currentTimeMillis()
                + proxyIndex++);

        ClassFile ccFile = cc.getClassFile();
        ConstPool constpool = ccFile.getConstPool();

        // create the annotation
        AnnotationsAttribute attr = new AnnotationsAttribute(constpool,
                AnnotationsAttribute.visibleTag);
        // 创建JsonIgnoreProperties注解
        Annotation jsonIgnorePropertiesAnnotation = new Annotation(
                JsonIgnoreProperties.class.getName(), constpool);

        BooleanMemberValue ignoreUnknownMemberValue = new BooleanMemberValue(false, constpool);

        ArrayMemberValue arrayMemberValue = new ArrayMemberValue(constpool);// value的数组成员

        Collection<MemberValue> memberValues = new HashSet<MemberValue>();
        for (int i = 0; i < names.length; i++) {
            String name = names[i];
            StringMemberValue memberValue = new StringMemberValue(constpool);// 将name值设入注解内
            memberValue.setValue(name);
            memberValues.add(memberValue);
        }
        arrayMemberValue.setValue(memberValues.toArray(new MemberValue[]{}));

        jsonIgnorePropertiesAnnotation.addMemberValue("value", arrayMemberValue);
        jsonIgnorePropertiesAnnotation.addMemberValue("ignoreUnknown", ignoreUnknownMemberValue);

        attr.addAnnotation(jsonIgnorePropertiesAnnotation);
        ccFile.addAttribute(attr);

        // generate the class
        try {
            clazz = cc.toClass();
            proxyMixInAnnotationMap.put(StringHelper.hashCodeOfStringArray(names), clazz);
            // JsonIgnoreProperties ignoreProperties = (JsonIgnoreProperties)
            // clazz
            // .getAnnotation(JsonIgnoreProperties.class);

            // EntityHelper.print(ignoreProperties);
            //
            // EntityHelper.print(clazz);

            // try {
            // Object instance = clazz.newInstance();
            // EntityHelper.print(instance);
            //
            // } catch (InstantiationException e) {
            // e.printStackTrace();
            // } catch (IllegalAccessException e) {
            // e.printStackTrace();
            // }
        } catch (CannotCompileException e) {
            e.printStackTrace();
        }

        // right
        // mthd.getMethodInfo().addAttribute(attr);

        return clazz;

    }


    @Override
    public Object filterProperties(Method method, Object object) {

        Map<Class<?>, Class<?>> map = getProxyMixInAnnotation(method);
        if (map == null || map.entrySet().size() == 0) {// 如果该方法上没有注解,则返回原始对象
            return object;
        }

//      Set<Entry<Class<?>, Class<?>>> entries = map.entrySet();
//      for (Iterator<Entry<Class<?>, Class<?>>> iterator = entries.iterator(); iterator.hasNext();) {
//          Entry<Class<?>, Class<?>> entry = (Entry<Class<?>, Class<?>>) iterator.next();
//          //          EntityHelper.print(entry.getKey());
//          Class<?> clazz = entry.getValue();
//          //          EntityHelper.print(clazz.getAnnotation(JsonIgnoreProperties.class));
//      }


//      ObjectMapper mapper = createObjectMapper(map);
        ThreadJacksonMixInHolder.addMixIns(getEntries(map));
//      try {
//          HttpServletResponse response = WebContext.getInstance().getResponse();
//          writeJson(mapper, response, object);
//      } catch (WebContextAlreadyClearedException e) {
//          e.printStackTrace();
//      }

        return object;
    }

    public Set<Entry<Class<?>, Class<?>>> getEntries(Map<Class<?>, Class<?>> map) {
        Set<Entry<Class<?>, Class<?>>> entries = map.entrySet();
        return entries;
    }

    /**
     * 根据指定的过滤表创建jackson对象 <br>
     * 2013-10-25 下午2:46:43
     *
     * @param map 过滤表
     * @return ObjectMapper
     */
    private ObjectMapper createObjectMapper(Map<Class<?>, Class<?>> map) {
        ObjectMapper mapper = new ObjectMapper();
        Set<Entry<Class<?>, Class<?>>> entries = map.entrySet();
        for (Iterator<Entry<Class<?>, Class<?>>> iterator = entries.iterator(); iterator.hasNext(); ) {
            Entry<Class<?>, Class<?>> entry = iterator.next();
            mapper.addMixInAnnotations(entry.getKey(), entry.getValue());
        }
        return mapper;
    }

    /**
     * 根据方法上的注解生成objectMapper
     *
     * @param method
     * @return
     */
    public ObjectMapper createObjectMapper(Method method) {
        return createObjectMapper(getProxyMixInAnnotation(method));
    }

//    /**
//     * 将结果输出到response <br>
//     * 2013-10-25 下午2:28:40
//     *
//     * @param objectMapper
//     * @param response
//     * @param object
//     */
//    private void writeJson(ObjectMapper objectMapper, HttpServletResponse response, Object object) {
//        response.setContentType("application/json");
//
//        JsonEncoding encoding = getJsonEncoding(response.getCharacterEncoding());
//        JsonGenerator jsonGenerator = null;
//        try {
//            jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(
//                    response.getOutputStream(), encoding);
//        } catch (IOException e1) {
//            e1.printStackTrace();
//        }
//
//        // A workaround for JsonGenerators not applying serialization features
//        // https://github.com/FasterXML/jackson-databind/issues/12
//        if (objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
//            jsonGenerator.useDefaultPrettyPrinter();
//        }
//
//        try {
//            objectMapper.writeValue(jsonGenerator, object);
//        } catch (JsonProcessingException ex) {
//            LOGGER.error(ex);
//
//            throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(),
//                    ex);
//        } catch (IOException e) {
//            LOGGER.error(e);
//            // e.printStackTrace();
//        }
//
//    }

    /**
     * <br>
     * 2013-10-25 下午12:29:58
     *
     * @param characterEncoding
     * @return
     */
    private JsonEncoding getJsonEncoding(String characterEncoding) {
        for (JsonEncoding encoding : JsonEncoding.values()) {
            if (characterEncoding.equals(encoding.getJavaName())) {
                return encoding;
            }
        }
        return JsonEncoding.UTF8;
    }

    /**
     * Determine the JSON encoding to use for the given content type.
     *
     * @param contentType the media type as requested by the caller
     * @return the JSON encoding to use (never {@code null})
     */
    protected JsonEncoding getJsonEncoding(MediaType contentType) {
        if (contentType != null && contentType.getCharSet() != null) {
            Charset charset = contentType.getCharSet();
            for (JsonEncoding encoding : JsonEncoding.values()) {
                if (charset.name().equals(encoding.getJavaName())) {
                    return encoding;
                }
            }
        }
        return JsonEncoding.UTF8;
    }

}

线程持有类,用于在当前线程内保存核心类处理过的自定义注解生成的MixIn注解,并且能提供ObjectMapper的生成

package com.xiongyingqi.jackson.helper;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 在当前线程内保存ObjectMapper供Jackson2HttpMessageConverter使用
 * Created by 瑛琪<a href="http://xiongyingqi.com">xiongyingqi.com</a> on 2014/4/1 0001.
 */
public class ThreadJacksonMixInHolder {
    private static ThreadLocal<ThreadJacksonMixInHolder> holderThreadLocal = new ThreadLocal<ThreadJacksonMixInHolder>();
    private Set<Map.Entry<Class<?>, Class<?>>> mixIns;
    private ObjectMapper mapper;
    private org.codehaus.jackson.map.ObjectMapper codehausMapper;

    /**
     * 根据当前MixIn集合生成objectMapper<p>
     * <p/>
     * <b>注意:该方法在返回mapper对象之后调用clear方法,如果再次调用builderMapper()肯定会保存</b>
     *
     * @return
     */
    public static ObjectMapper builderMapper() {
        ThreadJacksonMixInHolder holder = holderThreadLocal.get();
        if (holder.mapper == null && isContainsMixIn()) {
            holder.mapper = new ObjectMapper();
            for (Map.Entry<Class<?>, Class<?>> mixIn : holder.mixIns) {
                holder.mapper.addMixInAnnotations(mixIn.getKey(), mixIn.getValue());
            }
        }
        clear();// 如果不调用clear可能导致线程内的数据是脏的!
        return holder.mapper;
    }

    /**
     * 根据当前MixIn集合生成objectMapper
     *
     * @return
     */
    public static org.codehaus.jackson.map.ObjectMapper builderCodehausMapper() {
        ThreadJacksonMixInHolder holder = holderThreadLocal.get();
        if (holder.codehausMapper == null && isContainsMixIn()) {
            holder.codehausMapper = new org.codehaus.jackson.map.ObjectMapper();
            for (Map.Entry<Class<?>, Class<?>> mixIn : holder.mixIns) {
                holder.codehausMapper.getDeserializationConfig().addMixInAnnotations(mixIn.getKey(), mixIn.getValue());
                holder.codehausMapper.getSerializationConfig().addMixInAnnotations(mixIn.getKey(), mixIn.getValue());
            }
        }
        clear();// 如果不调用clear可能导致线程内的数据是脏的!
        return holder.codehausMapper;
    }

    /**
     * 清除当前线程内的数据
     */
    public static void clear() {
        holderThreadLocal.set(null);
//        holderThreadLocal.remove();
    }

    /**
     * 设置MixIn集合到线程内,如果线程内已经存在数据,则会先清除
     *
     * @param resetMixIns
     */
    public static void setMixIns(Set<Map.Entry<Class<?>, Class<?>>> resetMixIns) {
        ThreadJacksonMixInHolder holder = holderThreadLocal.get();
        if (holder == null) {
            holder = new ThreadJacksonMixInHolder();
            holderThreadLocal.set(holder);
        }
        holder.mixIns = resetMixIns;
    }

    /**
     * 不同于setMixIns,addMixIns为增加MixIn集合到线程内,即不会清除已经保存的数据
     * <br>2014年4月4日 下午12:08:15
     *
     * @param toAddMixIns
     */
    public static void addMixIns(Set<Map.Entry<Class<?>, Class<?>>> toAddMixIns) {
        ThreadJacksonMixInHolder holder = holderThreadLocal.get();
        if (holder == null) {
            holder = new ThreadJacksonMixInHolder();
            holderThreadLocal.set(holder);
        }
        if (holder.mixIns == null) {
            holder.mixIns = new HashSet<Map.Entry<Class<?>, Class<?>>>();
        }
        holder.mixIns.addAll(toAddMixIns);
    }

    /**
     * 获取线程内的MixIn集合<p></p>
     * <b>注意:为了防止线程执行完毕之后仍然存在有数据,请务必适时调用clear()方法</b>
     *
     * @return
     * @see com.xiongyingqi.jackson.helper.ThreadJacksonMixInHolder#builderMapper()
     * @see com.xiongyingqi.jackson.helper.ThreadJacksonMixInHolder#builderCodehausMapper()
     * @see com.xiongyingqi.jackson.helper.ThreadJacksonMixInHolder#clear()
     */
    public static Set<Map.Entry<Class<?>, Class<?>>> getMixIns() {
        ThreadJacksonMixInHolder holder = holderThreadLocal.get();
        return holder.mixIns;
    }

    /**
     * 判断当前线程是否存在MixIn集合
     *
     * @return
     */
    public static boolean isContainsMixIn() {
        if (holderThreadLocal.get() == null) {
            return false;
        }
        if (holderThreadLocal.get().mixIns != null && holderThreadLocal.get().mixIns.size() > 0) {
            return true;
        }
        return false;
    }

}

测试

测试代码

package com.xiongyingqi.jackson;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xiongyingqi.jackson.annotation.IgnoreProperties;
import com.xiongyingqi.jackson.annotation.IgnoreProperty;
import com.xiongyingqi.jackson.helper.ThreadJacksonMixInHolder;
import com.xiongyingqi.jackson.impl.JavassistFilterPropertyHandler;
import com.xiongyingqi.jackson.pojo.Group;
import com.xiongyingqi.jackson.pojo.User;
import com.xiongyingqi.util.Assert;
import com.xiongyingqi.util.EntityHelper;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Collection;

/**
 * Created by 瑛琪<a href="http://xiongyingqi.com">xiongyingqi.com</a> on 2014/6/4 0004.
 */
public class JsonFilterPropertyTest {

    @IgnoreProperties(@IgnoreProperty(pojo = User.class, name = "id"))
    public Collection<User> listUsers() {
        Group group1 = new Group();
        group1.setId(1);
        group1.setName("分组1");

        User user1 = new User();
        user1.setId(1);
        user1.setGroup(group1);
        user1.setName("用户1");
        User user2 = new User();
        user2.setId(1);
        user2.setGroup(group1);
        user2.setName("用户1");
        User user3 = new User();
        user3.setId(1);
        user3.setName("用户1");
        user3.setGroup(group1);


        Group group2 = new Group();
        group2.setId(2);
        group2.setName("分组2");

        User user4 = new User();
        user4.setId(4);
        user4.setGroup(group2);
        user4.setName("用户4");
        User user5 = new User();
        user5.setId(5);
        user5.setGroup(group2);
        user5.setName("用户5");
        User user6 = new User();
        user6.setId(6);
        user6.setName("用户6");
        user6.setGroup(group2);

        Collection<User> users = new ArrayList<User>();
        users.add(user1);
        users.add(user2);
        users.add(user3);
        users.add(user4);
        users.add(user5);
        users.add(user6);
        return users;
    }

    @Test
    public void jsonTest() throws NoSuchMethodException, JsonProcessingException {
        FilterPropertyHandler filterPropertyHandler = new JavassistFilterPropertyHandler(false);
        Object object = listUsers();

        object = filterPropertyHandler.filterProperties(JsonFilterPropertyTest.class.getMethod("listUsers"), object);


        ObjectMapper mapper = ThreadJacksonMixInHolder.builderMapper();
        String json = mapper.writeValueAsString(object);
        EntityHelper.print(json);
        Assert.hasText(json);
    }
}

测试结果


at com.xiongyingqi.jackson.JsonFilterPropertyTest.jsonTest(JsonFilterPropertyTest.java:80)
String =============== [{"name":"用户1","group":{"id":1,"name":"分组1"}},{"name":"用户1","group":{"id":1,"name":"分组1"}},{"name":"用户1","group":{"id":1,"name":"分组1"}},{"name":"用户4","group":{"id":2,"name":"分组2"}},{"name":"用户5","group":{"id":2,"name":"分组2"}},{"name":"用户6","group":{"id":2,"name":"分组2"}}]

性能与缺陷

  • 主要是在map内存储了Javassist的临时类,每个注解(IgnoreProperties等)的方法的调用,对应在FilterPropertyHandler会处理一次注解并在内存内产生一个Javassist临时类,但是访问过一次之后该类就会读取map缓存
  • ThreadJacksonMixInHolder:这个类的原理就是使用ThreadLocal在当前线程内存储处理过的annotation注解,java的容器或框架都是使用了该类,导致的效率问题应该不大
  • 未知的bug

其他说明

其他框架内使用 如果不是spring-mvc框架也能使用这些代码来解决,只是必须要修改aop的捕获方法、使用new JavassistFilterPropertyHandler(false)禁用ResponseBody,以及在ObjectMapper输出使用自己定义的输出

源代码地址

代码已上传到maven中央库:

http://mvnrepository.com/artifact/com.xiongyingqi/common_helper

Maven Usage:

<dependency>
    <groupId>com.xiongyingqi</groupId>
    <artifactId>common_helper</artifactId>
    <version>${common_utils.version}</version>
</dependency>