Spring MVC中使用jackson的MixInAnnotations方法動態過濾JSON字段

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<DepartGenInfo> 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;  

<pre name="code" class="java">      //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

<bean id="xunerJsonFilterAdvice" class="com.xunersoft.common.json.XunerJsonFilterAdvice"/>  

      

    <aop:config>  

        <aop:aspect id="jsonFilterAspect" ref="xunerJsonFilterAdvice">  

            <aop:pointcut id="jsonFilterPointcut" expression="execution(* com.xunersoft.webapp.rsgl.controller.*.*(..))"/>  

            <aop:around pointcut-ref="jsonFilterPointcut" method="doAround"/>  

        </aop:aspect>  

    </aop:config>  

 

其中pointcut的expression能夠匹配到目標類的方法。

在doAround方法中,需要獲得當前引用的HttpResponse對象,因此使用以下方法解決:

 

創建一個WebContext工具類:

 

[java] view plaincopy

public class WebContext {  

  

    private static ThreadLocal<WebContext> tlv = new ThreadLocal<WebContext>();  

    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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章