动态json输出(原创)

一、背景:

在SpringMVC项目的controller层中,通常通过ResponseBody注解实现将一个DTO对象序列化成json字符串输出到前端,但在实际很多情况下不同接口都是输出同一个大的DTO对象中的部分字段信息,比如包含人员信息的DTO对象employeeDTO,第一个接口只需要输出人员基本信息,第二个接口只需要输出人员的联系信息,第三个接口只需要输出人员的工作经历,第四个接口只需要输出人员的教育信息,等等其他接口分别输出不同的信息,通常的做法是针对每种输出定义一个employeeDTO对象的子DTO对象(只包含其部分信息),然后在每个controller接口里通过BeanMapper进行对象转换,同时还涉及到对象的封包和解包,这样做带来三个弊端:

1、代码重复冗余;

2、不用接口的转换要先复制代码再修改部分细节,容易出错;

3、不易扩展,以后要新增一个接口或者增加删除部分字段信息时,设计到大量代码的修改;

动态json输出技术,只需要在controller接口上添加一个注解,在注解中配置好不输出的字段信息,实现对代码的无侵入性,减少重复代码,增加删除字段以及增加接口只需要修改下不输出字段信息列表即可,扩展非常方便;

二、jackson动态序列化:

Jackson是一个简单基于Java应用库,Jackson可以轻松的将Java对象转换成json对象和xml文档,同样也可以将json、xml转换成Java对象。Jackson所依赖的jar包较少,简单易用并且性能也要相对高些,并且Jackson社区相对比较活跃,更新速度也比较快。

Jackson提供了一系列注解,方便对JSON序列化和反序列化进行控制,下面介绍一些常用的注解。

1、@JsonIgnore 此注解用于属性上,作用是进行JSON操作时忽略该属性;

2、@JsonFormat 此注解用于属性上,作用是把Date类型直接转化为想要的格式,如@JsonFormat(pattern = "yyyy-MM-dd HH-mm-ss")。

3、@JsonProperty 此注解用于属性上,作用是把该属性的名称序列化为另外一个名称,如把trueName属性序列化为name,@JsonProperty("name")。

针对上面的问题,可以在DTO对象上添加jackson注解来实现动态输出,但是这种方案会有代码侵入性,尤其是DTO对象还是从其他模块引入时,无法修改别人的DTO定义代码时,这种方案则无能为力了,因此这里我们采用了一种通过Spring AOP技术实现的无代码侵入性的动态json输出;

三、实现自定义注解:

首先要实现自定义注解,在这个注解里面包含要忽略字段的DTO对象和要忽略的属性列表,代码如下:

 

 1 @Retention(RetentionPolicy.RUNTIME)
 2 public @interface JsonFieldFilter {
 3     Class<?> mixin() default Object.class;
 4     Class<?> target() default Object.class;
 5 }
 6 
 7 @Retention(RetentionPolicy.RUNTIME)
 8 public @interface JsonFieldFilters {
 9     JsonFieldFilter[] value();
10 }

 

四、切面中实现动态json输出:

1、定义切面类,指定要切入的方法;

关键代码:

1 @Around("execution(public * com.*.*.*.controller.EmployeeController.*(..))")

2、扫描切面方法上是否有上面定义的自定义注解,只有在定义了注解的情况下才需要修改json输出,否则按原来的输出方式进行输出;

关键代码:

1 MethodSignature msig = (MethodSignature) pjp.getSignature();
2 JsonFieldFilter jsonFieldFilter = msig.getMethod().getAnnotation(JsonFieldFilter.class);
3 if (jsonFieldFilter == null) {
4     return pjp.proceed();
5 }

3、读取自定义注解上的目标DTO对象和要忽略的属性列表,将其设置到ObjectMapper对象中并输出json到Response对象中;

关键代码:

1 ObjectMapper mapper = new ObjectMapper();
2 Class<?> mixin = jsonFieldFilter.mixin();
3 Class<?> target = jsonFieldFilter.target();
4 if (target != null) {
5     mapper.addMixInAnnotations(target, mixin);
6 } else {
7     mapper.addMixInAnnotations(msig.getMethod().getReturnType(), mixin);
8 }
9 mapper.writeValue(response.getOutputStream(), pjp.proceed());

4、针对每一种不同输出的接口定义一个注解,注解中指定要忽略的字段属性列表;

关键代码:

1 @JsonIgnoreProperties(value={"accountStatus", "level", "gender",
2                             "startDate", "endDate", "pLoginName", "pUserName"})
3 public interface EmployeeDetailInfoFilter {
4 }

5、在controller接口方法上添加注解;

关键代码:

1 @JsonFieldFilter(mixin=EmployeeDetailInfoFilter.class, target=EmployeeVODTO.class)

切面的完整代码如下:

 1 @Aspect
 2 @Component
 3 @EnableAspectJAutoProxy
 4 public class JsonFilterAspect {
 5 
 6     private final static Logger LOGGER = LoggerFactory.getLogger(JsonFilterAspect.class);
 7 
 8     @Around("execution(public * com.*.*.*.controller.EmployeeController.*(..))")
 9     public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
10         MethodSignature msig = (MethodSignature) pjp.getSignature();
11         JsonFieldFilter jsonFieldFilter = msig.getMethod().getAnnotation(JsonFieldFilter.class);
12         if (jsonFieldFilter == null) {
13             return pjp.proceed();
14         }
15 
16         HttpServletResponse response = null;
17         Object[] args = pjp.getArgs();
18         if ((args.length > 0) && (args[0] instanceof HttpServletResponse)) {
19             response = (HttpServletResponse)args[0];
20         }
21         if (response == null) {
22             return pjp.proceed();
23         }
24 
25         try {
26             ObjectMapper mapper = new ObjectMapper();
27             Class<?> mixin = jsonFieldFilter.mixin();
28             Class<?> target = jsonFieldFilter.target();
29             if (target != null) {
30                 mapper.addMixInAnnotations(target, mixin);
31             } else {
32                 mapper.addMixInAnnotations(msig.getMethod().getReturnType(), mixin);
33             }
34 
35             response.setHeader("Content-Type", "application/json;charset=UTF-8");
36             mapper.writeValue(response.getOutputStream(), pjp.proceed());
37 
38             return null;
39         } catch (Exception ex) {
40             LOGGER.error("返回输出json失败,错误信息:" + ex.getMessage(), ex);
41         }
42 
43         return pjp.proceed();
44     }
45 
46 }

需要注意两点:

1、由于要将DTO对象序列化为json字符串并输出到前端,因此需要获取Response对象,所以上面代码中约定好接口的第一个参数为HttpResponse对象;

2、由于jackson版本的原因,可能在低版本的jackson中输出中文时会有乱码,因此需要在输出前添加下面的设置代码,以保证中文输出不会乱码:

1 response.setHeader("Content-Type", "application/json;charset=UTF-8");

 

上一篇:Spring AOP 拦截指定注解标识的类或方法


下一篇:后端接受数据流处理数据(不完善)