Spring MVC中使用jackson的MixInAnnotations方法动态过滤JSON字段

一、问题的提出。

 

项目使用Spring MVC框架,并用jackson库处理JSON和POJO的转换。在POJO转化成JSON时,希望动态的过滤掉对象的某些属性。所谓动态,是指的运行时,不同的controler方法可以针对同一POJO过滤掉不同的属性。

 

以下是一个Controler方法的定义,使用@ResponseBody把获得的对象列表写入响应的输出流(当然,必须配置jackson的MappingJacksonHttpMessageConverter,来完成对象的序列化)

[java] view plaincopy

@RequestMapping(params = "method=getAllBmForList")  

@ResponseBody  

public List getAllBmForList(HttpServletRequest request,  

        HttpServletResponse response) throws Exception {  

      

    BmDto dto = bmglService.getAllBm();  

    return dto.getBmList();  

}  

 

POJO定义如下

[java] view plaincopy

public class DepartGenInfo implements java.io.Serializable {  

  

     private String depid;  

     private String name;  

     private Company company;  

  

     //getter...  

     //setter...  

}   

  

public class Company  {  

  

     private String comid;  

     private String name;  

      //getter...  

     //setter...  

}  

我希望在getAllBmForList返回时,过滤掉DepartGenInfo的name属性,以及company的comid属性。

jackson支持@JsonIgnore和@JsonIgnoreProperties注解,但是无法实现动态过滤。jackson给出了几种动态过滤的办法,我选择使用annotation mixin

 

JSON View

JSON Filter

Annotation Mixin

二、使用annotation mixin动态过滤

 

[java] view plaincopy

@RequestMapping(params = "method=getAllBmForList")  

public void getAllBmForList(HttpServletRequest request,  

        HttpServletResponse response) throws Exception {  

      

    BmDto dto = bmglService.getAllBm();  

     

    ObjectMapper mapper = new ObjectMapper();  

    SerializationConfig serializationConfig = mapper.getSerializationConfig();  

    serializationConfig.addMixInAnnotations(DepartGenInfo.class,  

      DepartGenInfoFilter.class);  

  

    serializationConfig.addMixInAnnotations(Company.class,  

      CompanyFilter.class);  

      

    mapper.writeValue(response.getOutputStream(),dto.getBmList());  

    return;  

}  

 

DepartGenInfoFilter的定义如下:

 

[java] view plaincopy

@JsonIgnoreProperties(value={"name"}) //希望动态过滤掉的属性  

public interface DepartGenInfoFilter {  

}  

CompanyFilter的定义如下:

 

[java] view plaincopy

@JsonIgnoreProperties(value={"comid"}) //希望动态过滤掉的属性  

public interface CompanyFilter{   

}  

 

这样处理便能够动态过滤属性。如果需要修改过滤的属性,只需要定义新的一个"Filter”,然后使用

 

[java] view plaincopy

serializationConfig.addMixInAnnotations();  

这个实现方法看起来非常不简洁,需要在动态过滤的时候写不少代码,而且也改变了@ResponseBody的运行方式,失去了REST风格,因此考虑到使用AOP来进行处理。

二、最终解决方案

先看下我想达到的目标,通过自定义注解的方式来控制动态过滤。

[java] view plaincopy

@XunerJsonFilters(value={@XunerJsonFilter(mixin=DepartGenInfoFilter.class, target=DepartGenInfo.class)  

            ,@XunerJsonFilter(mixin=CompanyFilter.class, target=Company.class)})  

    @RequestMapping(params = "method=getAllBmForList")  

    @ResponseBody  

    public List getAllBmForList(HttpServletRequest request,  

            HttpServletResponse response) throws Exception {  

          

        BmDto dto = bmglService.getAllBm();  

return dto.getBmList();  

    }  

 

 

 

@XunerJsonFilters和@XunerJsonFilter是我定义的注解。@XunerJsonFilters是@XunerJsonFilter的集合,@XunerJsonFilter定义了混合的模板以及目标类。

 

[java] view plaincopy

@Retention(RetentionPolicy.RUNTIME)  

public @interface XunerJsonFilters {  

    XunerJsonFilter[] value();  

}  

@Retention(RetentionPolicy.RUNTIME)  

public @interface XunerJsonFilter {  

  Class mixin() default Object.class;  

  Class target() default Object.class;  

}  

 

当然,只是定义注解并没有什么意义。重要的是如何根据自定义的注解进行处理。我定义了一个AOP Advice如下:

 

[java] view plaincopy

public class XunerJsonFilterAdvice {  

  

    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {  

        MethodSignature msig = (MethodSignature) pjp.getSignature();  

        XunerJsonFilter annotation = msig.getMethod().getAnnotation(  

                XunerJsonFilter.class);  

        XunerJsonFilters annotations = msig.getMethod().getAnnotation(  

                XunerJsonFilters.class);  

  

        if (annotation == null && annotations == null) {  

            return pjp.proceed();  

        }  

  

        ObjectMapper mapper = new ObjectMapper();  

        if (annotation != null) {  

            Class mixin = annotation.mixin();  

            Class target = annotation.target();  

              

            if (target != null) {  

                mapper.getSerializationConfig().addMixInAnnotations(target,  

                        mixin);  

            } else {  

                mapper.getSerializationConfig().addMixInAnnotations(  

                        msig.getMethod().getReturnType(), mixin);  

            }  

        }  

          

        if (annotations != null) {  

            XunerJsonFilter[] filters= annotations.value();  

            for(XunerJsonFilter filter :filters){  

                Class mixin = filter.mixin();  

                Class target = filter.target();  

                  

                if (target != null) {  

                    mapper.getSerializationConfig().addMixInAnnotations(target,  

                            mixin);  

                } else {  

                    mapper.getSerializationConfig().addMixInAnnotations(  

                            msig.getMethod().getReturnType(), mixin);  

                }  

            }  

              

        }  

          

  

        try {  

            mapper.writeValue(WebContext.getInstance().getResponse()  

                    .getOutputStream(), pjp.proceed());  

        } catch (Exception ex) {  

            throw new RuntimeException(ex);  

        }  

        return null;  

    }  

  

}  

 

在Spring  MVC中进行AOP的配置

[html] view plaincopy

 

      

     

         

             

             

         

     

 

其中pointcut的expression能够匹配到目标类的方法。

在doAround方法中,需要获得当前引用的HttpResponse对象,因此使用以下方法解决:

 

创建一个WebContext工具类:

 

[java] view plaincopy

public class WebContext {  

  

    private static ThreadLocal tlv = new ThreadLocal();  

    private HttpServletRequest request;  

    private HttpServletResponse response;  

    private ServletContext servletContext;  

  

    protected WebContext() {  

    }  

  

    public HttpServletRequest getRequest() {  

        return request;  

    }  

  

    public void setRequest(HttpServletRequest request) {  

        this.request = request;  

    }  

  

    public HttpServletResponse getResponse() {  

        return response;  

    }  

  

    public void setResponse(HttpServletResponse response) {  

        this.response = response;  

    }  

  

    public ServletContext getServletContext() {  

        return servletContext;  

    }  

  

    public void setServletContext(ServletContext servletContext) {  

        this.servletContext = servletContext;  

    }  

  

    private WebContext(HttpServletRequest request,  

            HttpServletResponse response, ServletContext servletContext) {  

        this.request = request;  

        this.response = response;  

        this.servletContext = servletContext;  

    }  

  

    public static WebContext getInstance() {  

        return tlv.get();  

    }  

  

    public static void create(HttpServletRequest request,  

            HttpServletResponse response, ServletContext servletContext) {  

        WebContext wc = new WebContext(request, response, servletContext);  

        tlv.set(wc);  

    }  

  

    public static void clear() {  

        tlv.set(null);  

    }  

}  

 

定义一个Servlet Filter:

[java] view plaincopy

@Component("webContextFilter")  

public class WebContextFilter implements Filter {  

  

    public void init(FilterConfig filterConfig) throws ServletException {      

          

    }  

      

    public void doFilter(ServletRequest req, ServletResponse resp,  

            FilterChain chain) throws IOException, ServletException {  

        HttpServletRequest request = (HttpServletRequest) req;  

        HttpServletResponse response = (HttpServletResponse) resp;  

        ServletContext servletContext = request.getSession().getServletContext();  

        WebContext.create(request, response, servletContext);  

        chain.doFilter(request, response);  

        WebContext.clear();  

    }  

  

    @Override  

    public void destroy() {  

        // TODO Auto-generated method stub  

          

    }  

  

}  

 

别忘了在web.xml中增加这个filter。

 

OK,It is all。

 

 

四、总结

 

设计的一些要点:

 

1、要便于程序员使用。程序员根据业务逻辑需要过滤字段时,只需要定义个"Filter“,然后使用注解引入该Filter。

 

2、引入AOP来保持原来的REST风格。对于项目遗留的代码,不需要进行大幅度的修改,只需要增加注解来增加对过滤字段的支持。

 

仍需解决的问题:

 

按照目前的设计,定义的Filter不支持继承,每一种动态字段的业务需求就会产生一个Filter类,当类数量很多时,不便于管理。

 

 

五、参考资料

 

http://www.cowtowncoder.com/blog/archives/cat_json.html

 

http://www.jroller.com/RickHigh/entry/filtering_json_feeds_from_spring

 

转之 http://hi.baidu.com/suofang/item/5a10360cd04b6dc22e4c6b16