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


一、問題的提出。

項目使用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;
      //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的定義如下:

@JsonIgnoreProperties(value={"comid"}) //希望動態過濾掉的屬性
public interface CompanyFilter{	
}

這樣處理便能夠動態過濾屬性。如果需要修改過濾的屬性,只需要定義新的一個"Filter”,然後使用

 serializationConfig.addMixInAnnotations();

這個實現方法看起來非常不簡潔,需要在動態過濾的時候寫不少代碼,而且也改變了@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;
	}

}

在Spring  MVC中進行AOP的配置

<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工具類:

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:

@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






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