>

自定义注解中使用SpEL表达式

SpEL(Spring Expression Language),即Spring表达式语言。

一、SpEL的常见用法

1、 获取变量

获取变量的功能,比较常见的用法是spring中缓存注解的功能。可以动态获取方法中参数的值。如下:

    @Cacheable(value = "cacheKey",key = "#key")
    public String getName(String key) {
        return "name";
    }

2、执行方法

这个例子用过spring security的或者类似的授权框架的可能比较熟悉。如下

    @PreAuthorize("hasRole('ROLE_ADMIN')")
    public void addUser(User user) {
      System.out.println("addUser................" + user);
    }

这里其实就是通过表达式去执行某个类的hasRole方法,参数为’ROLE_ADMIN’,然后获得返回值再进行后续操作。

3、其他用法

表达式不仅支持获取属性和执行方法,还支持各种运算符操作,能查到很多其他大神写的。这里就不写了

二、自己实现表达式的运行

写demo之前先简单介绍一下执行表达式时比较重要的几个类

类名描述
ExpressionParser 表达式的解析器,主要用来生产表达式
Expression 表达式对象
EvaluationContext 表达式运行的上下文。包含了表达式运行时的参数,以及需要执行方法的类

1、获取变量

复制代码
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

/**
 *
 * @Author: dong
 * @Date: 2022/6/23 10:25
 */
public class Demo {

    public static void main(String[] args) {
        // 表达式解析器
        ExpressionParser parser = new SpelExpressionParser();
        // 解析出一个表达式
        Expression expression = parser.parseExpression("#user.name");
        // 开始准备表达式运行环境
        EvaluationContext ctx = new StandardEvaluationContext();
        ctx.setVariable("user", new User("三侃"));
        String value = expression.getValue(ctx, String.class);
        System.out.println(value);
    }

    public static class User{
        private String name;
        public User(String name){
            this.name = name;
        }
        public String getName(){
            return this.name;
        }
    }
}
复制代码

最后获得的value就是传入的值,这个demo已经可以通过表达式获取到user的name属性值了。

2、执行方法

复制代码
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

/**
 *
 * @Author: dong
 * @Date: 2022/6/23 10:25
 */
public class Demo {

    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression("getInput()");
        StandardEvaluationContext ctx = new StandardEvaluationContext();
        User user = new User("三侃");
        // 设置需要执行方法的类
        ctx.setRootObject(user);
        String value = expression.getValue(ctx, String.class);
        System.out.println(value);
    }

    public static class User{
        private String name;
        public User(String name){
            this.name = name;
        }
        public String getName(){
            return this.name;
        }
        public String getInput(){
            return "我叫:"+this.name;
        }
    }

}
复制代码

上面demo的运行结果是“我叫:三侃”,也就是调用getInput方法的结果

三、自定义注解并通过SpEL获取参值

demo是用java的动态代理实现的,所以中间会多出一个需要将接口方法转换为实现类方法的步骤,springAOP的话可以忽略。
User类

复制代码
public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}
复制代码

自定义注解

复制代码
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GetVal{

    String spel();
}
复制代码

接口类和其实现类

复制代码
public interface Run {

    void run(User user);
}

public class RunImpl implements Run {


    @GetVal(spel = "#user.name")
    @Override
    public void run(User user) {
        System.out.println(user.getName() + "正在跑");
    }
}
复制代码

代理类

复制代码
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 *
 * @Author: dong
 * @Date: 2022/6/23 13:46
 */
public class RunProxy implements InvocationHandler {

    private Object target;

    public RunProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 由于demo的动态代理使用接口无法获取到形参名称,所以转换为实现类的方法对象
        method = target.getClass().getMethod(method.getName(), method.getParameterTypes());
        Object res = method.invoke(target, args);
        DefaultParameterNameDiscoverer defaultParameterNameDiscoverer = new DefaultParameterNameDiscoverer();
        // 这里参考的org.springframework.context.expression.MethodBasedEvaluationContext.lazyLoadArguments
        String[] parameterNames = defaultParameterNameDiscoverer.getParameterNames(method);

        GetVal annotation = method.getAnnotation(GetVal.class);
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(annotation.spel());
        StandardEvaluationContext ctx = new StandardEvaluationContext();
        // 填充表达式上下文环境
        for(int i=0;i<parameterNames.length;i++){
            ctx.setVariable(parameterNames[i],args[i]);
        }
        String value = expression.getValue(ctx, String.class);
        // 打印日志
        System.out.println(value+"执行了"+method.getName());
        return res;
    }
}
复制代码

开始运行

public static void main(String[] args) {
        User user = new User("三侃");
        RunImpl runImpl = new RunImpl();
        RunProxy proxy = new RunProxy(runImpl);
        Run run = (Run) Proxy.newProxyInstance(runImpl.getClass().getClassLoader(), runImpl.getClass().getInterfaces(), proxy);
        run.run(user);
    }

可以看到控制台输处如下信息:

Connected to the target VM, address: '127.0.0.1:54442', transport: 'socket'
三侃正在跑
三侃执行了run
Disconnected from the target VM, address: '127.0.0.1:54442', transport: 'socket'

Process finished with exit code 0

结合springAOP+SpEL就能实现更加丰富的功能了

1、示例1

自定义注解

复制代码
package com.xxx.mall.order.service.component; 
import java.lang.annotation.*;
 
/**
 * 库存不足等信息监控
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface StockWarnCollect {
 
    /** 客户id */
    String customerId();
 
    /** 来源 */
    String source();
 
    /** 请求类型 1:详情页 2:购物车去结算 3:提交订单 */
    String pageType();
}
复制代码

注解使用

@Override
@StockWarnCollect(customerId = "#customerId", source = "#source", pageType = "2")
public Map validateCarts(Long customerId, Set userSelectedIds, Short source, JSONArray couponInfo){
    // 省略
}

aop中处理 

复制代码
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component; 
import java.lang.reflect.Method;
 
/**
 * 下单失败、库存监控
 */
@Aspect
@Component
@Slf4j
public class StockWarnCollectAop { 
    @Pointcut(value = "@annotation(com.xxx.mall.order.service.component.StockWarnCollect)")
    public void collectStockWarn(){}
 
    @Around(value = "collectStockWarn()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
 
        Method targetMethod = this.getTargetMethod(pjp);
        StockWarnCollect stockWarnCollect = targetMethod.getAnnotation(StockWarnCollect.class);
 
        // spel信息
        String customerIdSpel = stockWarnCollect.customerId();
        String sourceSpel = stockWarnCollect.source();
        Integer pageType = null;  // 操作类型,纯字符串
        if (StringUtils.isNotBlank(stockWarnCollect.pageType())) {
            pageType = Integer.valueOf(stockWarnCollect.pageType());
        }
 
        // 客户id、来源解析
        ExpressionParser parser = new SpelExpressionParser();
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] params = discoverer.getParameterNames(targetMethod); 
        Object[] args = pjp.getArgs();
 
        EvaluationContext context = new StandardEvaluationContext();
        for (int len = 0; len < params.length; len++) {
            context.setVariable(params[len], args[len]);
        }
        Expression expression = parser.parseExpression(customerIdSpel);
        Long customerId = expression.getValue(context, Long.class);
 
        expression = parser.parseExpression(sourceSpel);
        Short source = expression.getValue(context, Short.class);
        log.info("collectStockWarn customerId:{}, source:{}", customerId, source);
 
        // 业务逻辑处理
        Object result = null;
        try {
            result = pjp.proceed();
        } catch (Throwable e) {
            log.info("collectStockWarn watchs creating order errorMsg:{}", ExceptionUtils.getStackTrace(e));
            if (e instanceof MallException) {
 
            } else {    // 未知错误
            
            } 
            throw e;
        } 
        try {
            if (result != null) {            
            }
        } catch (Exception e) {
            log.error("collectStockWarn process error, errorMsg:{}", ExceptionUtils.getStackTrace(e));
        } 
        return result; 
    } 
    /**
     * 获取目标方法
     */
    private Method getTargetMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature)signature;
        Method agentMethod = methodSignature.getMethod();
        return pjp.getTarget().getClass().getMethod(agentMethod.getName(),agentMethod.getParameterTypes());
    }
}
复制代码

2、示例2

自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
    String module();
    String operation();
    String businessId() default "";
}

使用注解

@OperationLog(module = "role", operation = "insert", businessId = "#entity.name")
@PostMapping("/insert")
public void insert(Role entity) {

}

定义AOP

复制代码
@Service
@Aspect
public class OperationLogAop {
    @After("@annotation(operationLog)")
    public void interceptOperation(JoinPoint point, OperationLog operationLog) {
        String module = operationLog.module();
        String operation = operationLog.operation();
        String businessIdSpel = operationLog.businessId();
        Object[] args = point.getArgs();
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer localVariableTable = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = localVariableTable.getParameterNames(method);
        //使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        //把方法参数放入SPEL上下文中
        for(int i=0;i<paraNameArr.length;i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        String businessId = null;
        // 使用变量方式传入业务动态数据
        if(businessIdSpel.matches("^#.*.$")) {
            businessId = parser.parseExpression(businessIdSpel).getValue(context, String.class);
        }
        System.out.println(businessId);
    }
}
复制代码

 

 

参考文章:

https://blog.csdn.net/weixin_42645678/article/details/125414902

https://www.ab62.cn/article/7713.html

 

posted @   字节悦动  阅读(4581)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示

目录导航