一、问题的提出。
项目使用Spring MVC框架,并用jackson库处理JSON和POJO的转换。在POJO转化成JSON时,希望动态的过滤掉对象的某些属性。所谓动态,是指的运行时,不同的controler方法可以针对同一POJO过滤掉不同的属性。
以下是一个Controler方法的定义,使用@ResponseBody把获得的对象列表写入响应的输出流(当然,必须配置jackson的MappingJacksonHttpMessageConverter,来完成对象的序列化)
|
1
2
3
4
5
6
7
8
|
@RequestMapping(params
= "method=getAllBmForList")@ResponseBodypublic List<DepartGenInfo>
getAllBmForList(HttpServletRequest request, HttpServletResponse
response) throws Exception
{ BmDto
dto = bmglService.getAllBm(); return dto.getBmList();} |
POJO定义
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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动态过滤
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@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定义了混合的模板以及目标类。
|
1
2
3
4
5
6
7
8
9
|
@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如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
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工具类:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
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
5万+

被折叠的 条评论
为什么被折叠?



