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

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

2012-02-13   CevenCheng   摘自 csdn博客  阅 12842  转 59

一、问题的提出。

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

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

[java]  view plain copy
  1. @RequestMapping(params = "method=getAllBmForList")  
  2. @ResponseBody  
  3. public List<DepartGenInfo> getAllBmForList(HttpServletRequest request,  
  4.         HttpServletResponse response) throws Exception {  
  5.       
  6.     BmDto dto = bmglService.getAllBm();  
  7.     return dto.getBmList();  
  8. }  

POJO定义如下

[java]  view plain copy
  1. public class DepartGenInfo implements java.io.Serializable {  
  2.   
  3.      private String depid;  
  4.      private String name;  
  5.      private Company company;  
  6.   
  7.      //getter...  
  8.      //setter...  
  9. }   
  10.   
  11. public class Company  {  
  12.   
  13.      private String comid;  
  14.      private String name;  
  15. <pre name="code" class="java">      //getter...  
  16.      //setter...  
  17. }  
 我希望在getAllBmForList返回时,过滤掉DepartGenInfo的name属性,以及company的comid属性。 

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

  • JSON View
  • JSON Filter
  • Annotation Mixin

二、使用annotation mixin动态过滤

[java]  view plain copy
  1. @RequestMapping(params = "method=getAllBmForList")  
  2. public void getAllBmForList(HttpServletRequest request,  
  3.         HttpServletResponse response) throws Exception {  
  4.       
  5.     BmDto dto = bmglService.getAllBm();  
  6.      
  7.     ObjectMapper mapper = new ObjectMapper();  
  8.     SerializationConfig serializationConfig = mapper.getSerializationConfig();  
  9.     serializationConfig.addMixInAnnotations(DepartGenInfo.class,  
  10.       DepartGenInfoFilter.class);  
  11.   
  12.     serializationConfig.addMixInAnnotations(Company.class,  
  13.       CompanyFilter.class);  
  14.       
  15.     mapper.writeValue(response.getOutputStream(),dto.getBmList());  
  16.     return;  
  17. }  

DepartGenInfoFilter的定义如下:

[java]  view plain copy
  1. @JsonIgnoreProperties(value={"name"}) //希望动态过滤掉的属性  
  2. public interface DepartGenInfoFilter {  
  3. }  

CompanyFilter的定义如下:

[java]  view plain copy
  1. @JsonIgnoreProperties(value={"comid"}) //希望动态过滤掉的属性  
  2. public interface CompanyFilter{   
  3. }  

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

[java]  view plain copy
  1. serializationConfig.addMixInAnnotations();  

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

二、最终解决方案

先看下我想达到的目标,通过自定义注解的方式来控制动态过滤。
[java]  view plain copy
  1. @XunerJsonFilters(value={@XunerJsonFilter(mixin=DepartGenInfoFilter.class, target=DepartGenInfo.class)  
  2.             ,@XunerJsonFilter(mixin=CompanyFilter.class, target=Company.class)})  
  3.     @RequestMapping(params = "method=getAllBmForList")  
  4.     @ResponseBody  
  5.     public List getAllBmForList(HttpServletRequest request,  
  6.             HttpServletResponse response) throws Exception {  
  7.           
  8.         BmDto dto = bmglService.getAllBm();  
  9. return dto.getBmList();  
  10.     }  



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

[java]  view plain copy
  1. @Retention(RetentionPolicy.RUNTIME)  
  2. public @interface XunerJsonFilters {  
  3.     XunerJsonFilter[] value();  
  4. }  
  5. @Retention(RetentionPolicy.RUNTIME)  
  6. public @interface XunerJsonFilter {  
  7.   Class<?> mixin() default Object.class;  
  8.   Class<?> target() default Object.class;  
  9. }  

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

[java]  view plain copy
  1. public class XunerJsonFilterAdvice {  
  2.   
  3.     public Object doAround(ProceedingJoinPoint pjp) throws Throwable {  
  4.         MethodSignature msig = (MethodSignature) pjp.getSignature();  
  5.         XunerJsonFilter annotation = msig.getMethod().getAnnotation(  
  6.                 XunerJsonFilter.class);  
  7.         XunerJsonFilters annotations = msig.getMethod().getAnnotation(  
  8.                 XunerJsonFilters.class);  
  9.   
  10.         if (annotation == null && annotations == null) {  
  11.             return pjp.proceed();  
  12.         }  
  13.   
  14.         ObjectMapper mapper = new ObjectMapper();  
  15.         if (annotation != null) {  
  16.             Class<?> mixin = annotation.mixin();  
  17.             Class<?> target = annotation.target();  
  18.               
  19.             if (target != null) {  
  20.                 mapper.getSerializationConfig().addMixInAnnotations(target,  
  21.                         mixin);  
  22.             } else {  
  23.                 mapper.getSerializationConfig().addMixInAnnotations(  
  24.                         msig.getMethod().getReturnType(), mixin);  
  25.             }  
  26.         }  
  27.           
  28.         if (annotations != null) {  
  29.             XunerJsonFilter[] filters= annotations.value();  
  30.             for(XunerJsonFilter filter :filters){  
  31.                 Class<?> mixin = filter.mixin();  
  32.                 Class<?> target = filter.target();  
  33.                   
  34.                 if (target != null) {  
  35.                     mapper.getSerializationConfig().addMixInAnnotations(target,  
  36.                             mixin);  
  37.                 } else {  
  38.                     mapper.getSerializationConfig().addMixInAnnotations(  
  39.                             msig.getMethod().getReturnType(), mixin);  
  40.                 }  
  41.             }  
  42.               
  43.         }  
  44.           
  45.   
  46.         try {  
  47.             mapper.writeValue(WebContext.getInstance().getResponse()  
  48.                     .getOutputStream(), pjp.proceed());  
  49.         } catch (Exception ex) {  
  50.             throw new RuntimeException(ex);  
  51.         }  
  52.         return null;  
  53.     }  
  54.   
  55. }  

在Spring  MVC中进行AOP的配置

[html]  view plain copy
  1. <bean id="xunerJsonFilterAdvice" class="com.xunersoft.common.json.XunerJsonFilterAdvice"/>  
  2.       
  3.     <aop:config>  
  4.         <aop:aspect id="jsonFilterAspect" ref="xunerJsonFilterAdvice">  
  5.             <aop:pointcut id="jsonFilterPointcut" expression="execution(* com.xunersoft.webapp.rsgl.controller.*.*(..))"/>  
  6.             <aop:around pointcut-ref="jsonFilterPointcut" method="doAround"/>  
  7.         </aop:aspect>  
  8.     </aop:config>  

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

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

创建一个WebContext工具类:

[java]  view plain copy
  1. public class WebContext {  
  2.   
  3.     private static ThreadLocal<WebContext> tlv = new ThreadLocal<WebContext>();  
  4.     private HttpServletRequest request;  
  5.     private HttpServletResponse response;  
  6.     private ServletContext servletContext;  
  7.   
  8.     protected WebContext() {  
  9.     }  
  10.   
  11.     public HttpServletRequest getRequest() {  
  12.         return request;  
  13.     }  
  14.   
  15.     public void setRequest(HttpServletRequest request) {  
  16.         this.request = request;  
  17.     }  
  18.   
  19.     public HttpServletResponse getResponse() {  
  20.         return response;  
  21.     }  
  22.   
  23.     public void setResponse(HttpServletResponse response) {  
  24.         this.response = response;  
  25.     }  
  26.   
  27.     public ServletContext getServletContext() {  
  28.         return servletContext;  
  29.     }  
  30.   
  31.     public void setServletContext(ServletContext servletContext) {  
  32.         this.servletContext = servletContext;  
  33.     }  
  34.   
  35.     private WebContext(HttpServletRequest request,  
  36.             HttpServletResponse response, ServletContext servletContext) {  
  37.         this.request = request;  
  38.         this.response = response;  
  39.         this.servletContext = servletContext;  
  40.     }  
  41.   
  42.     public static WebContext getInstance() {  
  43.         return tlv.get();  
  44.     }  
  45.   
  46.     public static void create(HttpServletRequest request,  
  47.             HttpServletResponse response, ServletContext servletContext) {  
  48.         WebContext wc = new WebContext(request, response, servletContext);  
  49.         tlv.set(wc);  
  50.     }  
  51.   
  52.     public static void clear() {  
  53.         tlv.set(null);  
  54.     }  
  55. }  

定义一个Servlet Filter:

[java]  view plain copy
  1. @Component("webContextFilter")  
  2. public class WebContextFilter implements Filter {  
  3.   
  4.     public void init(FilterConfig filterConfig) throws ServletException {      
  5.           
  6.     }  
  7.       
  8.     public void doFilter(ServletRequest req, ServletResponse resp,  
  9.             FilterChain chain) throws IOException, ServletException {  
  10.         HttpServletRequest request = (HttpServletRequest) req;  
  11.         HttpServletResponse response = (HttpServletResponse) resp;  
  12.         ServletContext servletContext = request.getSession().getServletContext();  
  13.         WebContext.create(request, response, servletContext);  
  14.         chain.doFilter(request, response);  
  15.         WebContext.clear();  
  16.     }  
  17.   
  18.     @Override  
  19.     public void destroy() {  
  20.         // TODO Auto-generated method stub  
  21.           
  22.     }  
  23.   
  24. }  

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


OK,It is all。


四、总结

设计的一些要点:

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

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

仍需解决的问题:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值