• 46. 从零开始学springboot: spel结合aop实现动态传参


    前言

    基于SpingBoot框架中, 我们随处可以见的便是各种各样的功能注解, 注解的实现原理AOP之前有说过(翻看本系列的前面几章即可), 这里不过多赘述.

    那么, 你有没有碰到这样一种场景: 需要动态的传参数进注解, 注意是动态的而不是写死在代码里的.

    针对这种需求, 今天, 我们就来实现一个简单的案例.

    SpEl表达式简介

    正式撸代码之前, 先了解下SpEl (Spring Expression Language) 表达式, 这是Spring框架中的一个利器.

    Spring通过SpEl能在运行时构建复杂表达式、存取对象属性、对象方法调用等等.

    举个简单的例子方便理解, 如下

    //定义了一个表达式
    String expressionStr = "1+1";
    ExpressionParser parser = new SpelExpressionParser();
    Expression expression = parser.parseExpression(expressionStr);
    Integer val = expression.getValue(Integer.class);
    System.out.println(expressionStr + "的结果是:" + val);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    通过以上案例, 不难理解, 所谓的SpEl, 本质上其实就是解析表达式,.

    关于SpEl表达式感兴趣的可以自行查阅资料, 本篇不做细致的讨论.

    实例: SpEl结合AOP动态传参

    简单了解了SpEl表达式, 那么接下来我们就直接开始撸代码.

    先引入必要的pom依赖, 其实只有aop依赖, SpEl本身就被Spring支持, 所以无需额外引入.

    
              org.springframework.boot
              spring-boot-starter-aop
    
    
    • 1
    • 2
    • 3
    • 4

    定义一个SpEl的工具类SpelUtil

    public class SpelUtil {
    
        /**
         * 用于SpEL表达式解析.
         */
        private static final SpelExpressionParser parser = new SpelExpressionParser();
    
        /**
         * 用于获取方法参数定义名字.
         */
        private static final DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
    
        /**
         * 解析SpEL表达式
         *
         * @param spELStr
         * @param joinPoint
         * @return
         */
        public static String generateKeyBySpEL(String spELStr, ProceedingJoinPoint joinPoint) {
            // 通过joinPoint获取被注解方法
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
            // 使用Spring的DefaultParameterNameDiscoverer获取方法形参名数组
            String[] paramNames = nameDiscoverer.getParameterNames(method);
            // 解析过后的Spring表达式对象
            Expression expression = parser.parseExpression(spELStr);
            // Spring的表达式上下文对象
            EvaluationContext context = new StandardEvaluationContext();
            // 通过joinPoint获取被注解方法的形参
            Object[] args = joinPoint.getArgs();
            // 给上下文赋值
            for (int i = 0; i < args.length; i++) {
                context.setVariable(paramNames[i], args[i]);
            }
            // 表达式从上下文中计算出实际参数值
            /*如:
                @annotation(key="#user.name")
                method(User user)
                 那么就可以解析出方法形参的某属性值,return “xiaoming”;
              */
            return expression.getValue(context).toString();
        }
    }
    
    • 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

    定义一个带参注解SpelGetParm

    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SpelGetParm {
    
        String parm() default "";
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    定义带参注解SpelGetParmAop

    @Aspect
    @Slf4j
    @Component
    public class SpelGetParmAop {
    
        @PostConstruct
        public void init() {
            log.info("SpelGetParm init ......");
        }
        /**
         * 拦截加了SpelGetParm注解的方法请求
         *
         * @param joinPoint
         * @param spelGetParm
         * @return
         * @throws Throwable
         */
        @Around("@annotation(spelGetParm)")
        public Object beforeInvoce(ProceedingJoinPoint joinPoint, SpelGetParm spelGetParm) throws Throwable {
            Object result = null;
            // 方法名
            String methodName = joinPoint.getSignature().getName();
            //获取动态参数
            String parm = SpelUtil.generateKeyBySpEL(spelGetParm.parm(), joinPoint);
            log.info("spel获取动态aop参数: {}", parm);
            try {
                log.info("执行目标方法: {} ==>>开始......", methodName);
                result = joinPoint.proceed();
                log.info("执行目标方法: {} ==>>结束......", methodName);
                // 返回通知
                log.info("目标方法 " + methodName + " 执行结果 " + result);
            } finally {
    
            }
            // 后置通知
            log.info("目标方法 " + methodName + " 结束");
            return result;
        }
    
    • 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

    以上已经基本实现了案例的核心功能, 接下来我们使用该注解即可

    定义一个实体User

    @Getter
    @Setter
    @NoArgsConstructor
    @JsonSerialize
    @JsonInclude(Include.NON_NULL)
    public class User implements Serializable {
        private static final long serialVersionUID = -7229987827039544092L;
    
        private String name;
        private Long id;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们在UserController直接使用该带参注解即可

    @CrossOrigin
    @RestController
    @RequestMapping("/user")
    public class UserController {
        @PostMapping("/param")
        @SpelGetParm(parm = "#user.name")
        public R repeat(@RequestBody User user) {
            return R.success(user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    最后请求
    image.png
    image.png

    可以看出, 切面成功获取到了实体的name值“张三”.

    小结

    结合SpEl表达式可以实现各种“骚操作”, 各位大佬可自由发挥, 下面一章我们准备结合SpEl来实现分布式锁的功能.

    项目地址

    https://github.com/MrCoderStack/SpringBootDemo/tree/master/sb-spel-annotations

    请关注我的订阅号

    订阅号.png

  • 相关阅读:
    C++学习:类的使用--运算符重载
    BYD精制项目除铜工艺去除铜离子
    计算机毕设 大数据商城人流数据分析与可视化 - python 大数据分析
    注意力机制讲解与代码解析
    2023最新SSM计算机毕业设计选题大全(附源码+LW)之java志愿者活动管理平台zx2tk
    nodejs(三)
    腾讯云发布智慧员工管理方案,支持组织360度协作
    怎么文字转语音真人发声?安利下面这三款软件
    Android-源码分析-MTK平台BUG解决:客户电池NTC功能(移植高低温报警,关机报警功能)---第三天分析与解决(已解决)
    dubbo+nacos服务器部署调用失败
  • 原文地址:https://blog.csdn.net/MrCoderStack/article/details/125996569