一、问题的提出。 

项目使用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的定义如下:



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

}



其中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类,当类数量很多时,不便于管理。