Spring spel获取自定义注解参数值方式
作者:公子
spel获取自定义注解参数值
1.注解类
package com.xxx.mall.order.service.component; import java.lang.annotation.*; /** * 库存不足等信息监控 * Created by xdc on 2019/4/16 15:43 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented public @interface StockWarnCollect { /** 客户id */ String customerId(); /** 来源 */ String source(); /** 请求类型 1:详情页 2:购物车去结算 3:提交订单 */ String pageType(); }
2.注解使用
@Override @StockWarnCollect(customerId = "#customerId", source = "#source", pageType = "2") public Map<String, Object> validateCarts(Long customerId, Set<Long> userSelectedIds, Short source, JSONArray couponInfo){ // 省略 }
3.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; /** * 下单失败、库存监控 * Created by xdc on 2019/4/16 15:45 */ @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()); } }
spel在注解中的使用
SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言。为什么要总结SpEL,因为它可以在运行时查询和操作数据,尤其是数组列表型数据,因此可以缩减代码量,优化代码结构。个人认为很有用。
1 语法说明
1.1 SpEL 字面量:
- 整数:#{8}
- 小数:#{8.8}
- 科学计数法:#{1e4}
- String:可以使用单引号或者双引号作为字符串的定界符号。
- Boolean:#{true}
1.2 SpEL引用bean , 属性和方法:
- 引用其他对象:#{car}
- 引用其他对象的属性:#{car.brand}
- 调用其它方法 , 还可以链式操作:#{car.toString()}
- 调用静态方法静态属性:#{T(java.lang.Math).PI}
1.3 SpEL支持的运算符号:
- 算术运算符:+,-,*,/,%,^(加号还可以用作字符串连接)
- 比较运算符:< , > , == , >= , <= , lt , gt , eg , le , ge
- 逻辑运算符:and , or , not , |
- if-else 运算符(类似三目运算符):?:(temary), ?:(Elvis)
- 正则表达式:#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}'}
2. 基本用法
SpEL有三种用法:
1. 是在注解@Value中;
2. 在XML配置中;
3. 代码块中使用Expression。
2.1 @Value
//@Value能修饰成员变量和方法形参 //#{}内就是表达式的内容 @Value("#{表达式}") public String arg;
2.2 <bean>配置
<bean id="xxx" class="com.java.XXXXX.xx"> <!-- 同@Value,#{}内是表达式的值,可放在property或constructor-arg内 --> <property name="arg" value="#{表达式}"> </bean>
2.3 代码块中使用
public class SpELTest { public static void main(String[] args) { //创建ExpressionParser解析表达式 ExpressionParser parser = new SpelExpressionParser(); //表达式放置 Expression exp = parser.parseExpression("表达式"); //执行表达式,默认容器是spring本身的容器:ApplicationContext Object value = exp.getValue(); /**如果使用其他的容器,则用下面的方法*/ //创建一个虚拟的容器EvaluationContext StandardEvaluationContext ctx = new StandardEvaluationContext(); //向容器内添加bean BeanA beanA = new BeanA(); ctx.setVariable("bean_id", beanA); //setRootObject并非必须;一个EvaluationContext只能有一个RootObject,引用它的属性时,可以不加前缀 ctx.setRootObject(XXX); //getValue有参数ctx,从新的容器中根据SpEL表达式获取所需的值 Object value = exp.getValue(ctx); } }
4 #{…}和${…}
- #{…} 用于执行SpEl表达式,并将内容赋值给属性
- ${…} 主要用于加载外部属性文件中的值
- #{…} 和${…} 可以混合使用,但是必须#{}外面,${}在里面
4.1 ${…}用法
{}里面的内容必须符合SpEL表达式,通过@Value(“${spelDefault.value}”)可以获取属性文件中对应的值,但是如果属性文件中没有这个属性,则会报错。可以通过赋予默认值解决这个问题,如@Value("${spelDefault.value:127.0.0.1}")
// 如果属性文件没有spelDefault.value,则会报错 // @Value("${spelDefault.value}") // private String spelDefault2; // 使用default.value设置值,如果不存在则使用默认值 @Value("${spelDefault.value:127.0.0.1}") private String spelDefault;
4.2 #{…}用法
这里只演示简单用法
// SpEL:调用字符串Hello World的concat方法 @Value("#{'Hello World'.concat('!')}") private String helloWorld; // SpEL: 调用字符串的getBytes方法,然后调用length属性 @Value("#{'Hello World'.bytes.length}") private String helloWorldbytes;
4.3 ${…}和#{…}混合使用
${...}和#{...}可以混合使用,如下文代码执行顺序:通过${server.name}从属性文件中获取值并进行替换,然后就变成了 执行SpEL表达式{‘server1,server2,server3’.split(‘,’)}。
// SpEL: 传入一个字符串,根据","切分后插入列表中, #{}和${}配置使用(注意单引号,注意不能反过来${}在外面,#{}在里面) @Value("#{'${server.name}'.split(',')}") private List<String> servers;
在上文中在#{}外面,${}在里面可以执行成功,那么反过来是否可以呢${}在外面,#{}在里面,如代码
// SpEL: 注意不能反过来${}在外面,#{}在里面,这个会执行失败 @Value("${#{'HelloWorld'.concat('_')}}") private List<String> servers2;
答案是不能。因为spring执行${}是时机要早于#{}。在本例中,Spring会尝试从属性中查找#{‘HelloWorld’.concat(‘_’)},那么肯定找不到,由上文已知如果找不到,然后报错。所以${}在外面,#{}在里面是非法操作
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。