一、問題的提出。
項目使用Spring MVC框架,並用jackson庫處理JSON和POJO的轉換。在POJO轉化成JSON時,希望動態的過濾掉對象的某些屬性。所謂動態,是指的運行時,不同的controler方法可以針對同一POJO過濾掉不同的屬性。
以下是一個Controler方法的定義,使用@ResponseBody把獲得的對象列表寫入響應的輸出流(當然,必須配置jackson的MappingJacksonHttpMessageConverter,來完成對象的序列化)
@RequestMapping(params = "method=getAllBmForList") @ResponseBody public List<DepartGenInfo> getAllBmForList(HttpServletRequest request, HttpServletResponse response) throws Exception { BmDto dto = bmglService.getAllBm(); return dto.getBmList(); }
POJO定義
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動態過濾
@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的定義如下:
@JsonIgnoreProperties(value={"name"}) //希望動態過濾掉的屬性 public interface DepartGenInfoFilter { }
//CompanyFilter的定義如下:
這個實現方法看起來非常不簡潔,需要在動態過濾的時候寫不少代碼,而且也改變了@ResponseBody的運行方式,失去了REST風格,因此考慮到使用AOP來進行處理。
二、最終解決方案
先看下我想達到的目標,通過自定義注解的方式來控制動態過濾。
@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定義了混合的模板以及目標類。
@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如下:
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; } }
其中pointcut的expression能夠匹配到目標類的方法。
在doAround方法中,需要獲得當前引用的HttpResponse對象,因此使用以下方法解決:
創建一個WebContext工具類:
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); } }
別忘了在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