您的位置:首页 > 编程语言 > Java开发

Spring MVC中使用jackson的MixInAnnotations方法动态过滤JSON字段

2015-06-10 14:17 597 查看
一、问题的提出。

项目使用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类,当类数量很多时,不便于管理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: