一、问题的提出。 项目使用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;
-
-
-
- }
-
- public class Company {
-
- private String comid;
- private String name;
- <pre name="code" class="java">
-
- }
我希望在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() {
-
-
- }
-
- }
别忘了在web.xml中增加这个filter。
OK,It is all。
四、总结 设计的一些要点: 1、要便于程序员使用。程序员根据业务逻辑需要过滤字段时,只需要定义个"Filter“,然后使用注解引入该Filter。 2、引入AOP来保持原来的REST风格。对于项目遗留的代码,不需要进行大幅度的修改,只需要增加注解来增加对过滤字段的支持。 仍需解决的问题: 按照目前的设计,定义的Filter不支持继承,每一种动态字段的业务需求就会产生一个Filter类,当类数量很多时,不便于管理。
五、参考资料 http://www./blog/archives/cat_json.html http://www./RickHigh/entry/filtering_json_feeds_from_spring
|