自定义注解中使用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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架