• 动态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文档,同样也可以将jsonxml转换成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");
  • 相关阅读:
    【UML建模】UML类图几种关系的总结
    【架构框架】IoC框架
    【AutoMapper基础】值解析器--Custom value resolvers
    【AutoMapper基础】简单示例--Flattening
    【AutoMapper简介】
    【UML建模】UML类图符号简介
    【.Net基础02】XML序列化问题
    【.net 基础01】ReferenceEquals,Equals,==的区别
    【Visual Studio】利用预编译命令发布不同的版本
    【Windows Phone 8】五角星评价控件
  • 原文地址:https://www.cnblogs.com/laoxia/p/11545571.html
Copyright © 2020-2023  润新知