使用Spring AOP修改方案入参实践

使用Spring AOP修改方案入参实践

Posted by yyconstantine on March 31, 2020

使用Spring AOP修改方案入参实践


业务需求是这样的:运营后台的新增/修改操作需要记录操作人,项目的session处理是全局持有一个userId来保持会话信息。这样会造成一个问题:想获取处理人信息时,需要通过userId获取到用户信息,再返回用户信息中的name作为操作人名称。每次都这样取很啰嗦(不太geek),所以预设优化方案如下:

  • 由于用户信息(尤其是姓名)是基本不会变的信息,所以userId作为key,userName作为value在Redis存储
  • 每次读取先从redis读取,读取不到再从数据库读取

上面的方案解决了每次用时查库的问题(实际运营后台操作量也不大,可以容忍= =),但每次需要手动获取,比较麻烦,于是想到通过aop的方式填充:

  • 首先定义一个基类:

    @Data
    public class BaseOperatorModel {
          
        private String createBy;
          
        private String updateBy;    
          
    }
    
  • 然后任意接口参数继承:

    @Data
    @EqualsAndHashCode(callSuper = true)
    public class ProductDTO extends BaseOperatorModel {
          
        private String productName;
          
        private Integer productType;
          
    }
    
  • 再定义一个切面类,这里我们选择切所有方法:

    @Aspect
    public class OperatorReplaceAspect {
        @Pointcut("execution(public * me.sxl.*.controller.*.*(..))")
        public void paramReplace() {
        }
          
        // 只有使用ProceedingJoinPoint,才能通过proceed(Object[])方法改变入参的值
        @Around("paramReplace()")
        public Object paramReplace(ProceedingJoinPoint pjp) throws Throwable {
            // 首先获取request,目的是获取session信息
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
      
            // 定义一个jackson序列化方式
            ObjectMapper mapper = new ObjectMapper();
            // 获取方法参数
            Object[] args = pjp.getArgs();
            // 定义接收参数的容器
            Map<String, Object> map = null;
            // 定义接收参数类型的class
            Class<?> clazz = null;
            // 定义获取args位置信息的index
            int index = -1;
            // for循环是针对存在多参数的情况
            for (int i = 0; i < args.length; i++) {
                // 由于参数存在继承关系,所以遍历到我们需要的参数时,这里返回true
                if (args[i] instanceof BaseOperatorModel) {
                    // 赋值class信息
                    clazz = args[i].getClass();
                    // 将该参数转换为map类型,方便处理
                    map = mapper.convertValue(args[i], Map.class);
                    // 获取index信息
                    index = i;
                    // 这里封装了通过userId获取userName的方法
                    String userName =
                            CommonUtils.getUserName((String) request.getSession().getAttribute(Constants.GLOBAL_USER_ID_KEY));
                    // 遍历map中的所有参数,找出名字为createBy/updateBy的参数,并对其赋值
                    for (Map.Entry entry : map.entrySet()) {
                        if (entry.getKey().equals(Constants.CREATE_BY_KEY)) {
                            map.put(Constants.CREATE_BY_KEY, userName);
                        }
                        if (entry.getKey().equals(Constants.UPDATE_BY_KEY)) {
                            map.put(Constants.UPDATE_BY_KEY, userName);
                        }
                    }
                }
            }
            // **下面很重要,如果不进行赋值操作及调用proceed方法,则入参改变不会生效**
            if (index >= 0) {
                args[index] = mapper.convertValue(map, clazz);
            }
            return pjp.proceed(args);
        }
    }
    
  • over