这里是weihubeats,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党
Spring EL表达式可以干什么
我们最常见的一些开源框架就经常会在注解中获取方法参数的值。
比如spring cache
@GetMapping("/test")
@Cacheable(cacheNames = "student", key = "#name")
public List<StudentVO> test(String name) {
return mockSelectSql();
}
这里的 #name
就是EL表达式。代表获取入参name的值
今天我们就来探究下Spring
的EL
表达式
Spring EL表达式的解析器 SpelExpressionParser
spring中实现EL表达式的解析器就是SpelExpressionParser
这个类
使用也非常简单。我们通过下面的几个用例来了解一下
SpelExpressionParser parser = new SpelExpressionParser();
String value = parser.parseExpression("'xiaozou'").getValue(String.class);// ,注意string有单引号包裹
Long value1 = parser.parseExpression("1.024E+3").getValue(Long.class);// 1024 , 指数形式
Integer value2 = parser.parseExpression("0x208").getValue(Integer.class);// 520 十六进制 0x208
Boolean value3 = parser.parseExpression("true").getValue(Boolean.class);// true
Object value4 = parser.parseExpression("null").getValue();
System.out.printf("value: %s\n value1: %s\n value2: %s\n value3: %s\n value4: %s\n", value, value1, value2, value3, value4);
对字符串和一些普通数字的解析好像没什么用。实际业务场景我也用不到。
对对象属性进行获取
SpelExpressionParser还可以获取对象的属性,比如向如下
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("name");
Person person = new Person("xiaozou");
String result = expression.getValue(person, String.class);
System.out.println(result); // Output: xiaozou
方法调用
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("toUpperCase()");
String str = "xiaozou";
String result = expression.getValue(str, String.class);
System.out.println(result); // Output: XIAOZOU
EL上下文 StandardEvaluationContext
当使用SpEL
时,可以结合StandardEvaluationContext
来设置根对象、变量和函数。
这样我们每次使用ExpressionParser
就无需传入具体的对象,只从StandardEvaluationContext
上下文中获取即可,实际spring
中的一些EL
表达式的实际使用都是结合StandardEvaluationContext
使用的。
这里我们也来看一些使用用例
// 创建 SpelExpressionParser 实例
SpelExpressionParser parser = new SpelExpressionParser();
// 创建 StandardEvaluationContext 实例
StandardEvaluationContext context = new StandardEvaluationContext();
// 在上下文中设置变量
context.setVariable("name", "John");
context.setVariable("age", 30);
// 解析表达式并计算结果
Expression expression = parser.parseExpression("'Hello, ' + #name + '! You are ' + #age + ' years old.'");
String result = expression.getValue(context, String.class);
// 输出结果 Hello, xiaozou! You are 18 years old.
System.out.println(result);
这种是直接放入常量,我们也可以放入对象
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("name");
Person person = new Person("xiao zou");
StandardEvaluationContext context = new StandardEvaluationContext(person);
String result = expression.getValue(context, String.class);
System.out.println(result); // Output: xiaozou
Spring 参数解析器
从上面的很多个例子中我们可以看到Spring
EL
可以获取对象的属性,我们在解析对象的时候一般会要对象的变量名。
也是就Spring
在AOP
中通过EL
表达式获取方法的参数的时候是需要参数名的,那么我们如何获取方法的参数名呢。
Spring提供了ParameterNameDiscoverer
接口。
我们看看ParameterNameDiscoverer
接口的实现类有哪些?
可以看到有非常多,比较常用的就是LocalVariableTableParameterNameDiscoverer
和DefaultParameterNameDiscoverer
.
LocalVariableTableParameterNameDiscoverer
是一种基于本地变量表的参数解析器,依赖于编译时的选项,可能无法在所有情况下获取参数名称。而DefaultParameterNameDiscoverer
是一个更通用的参数解析器,尝试多种策略来获取参数名称,能够在更广泛的情况下获取到参数名称,但是效率相对会低一点。
一般使用LocalVariableTableParameterNameDiscoverer
即可
这里我以一个简单的例子来看看
@Test
public void testParameterNameDiscoverer() throws Exception{
ParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
Method method = MyClass.class.getMethod("myMethod", String.class, int.class);
String[] parameterNames = discoverer.getParameterNames(method);
for (String parameterName : parameterNames) {
System.out.println("parameterName " + parameterName);// out parameterName name parameterName age
}
}
@Data
static class MyClass {
private String name = "Default";
private int age = 0;
public void myMethod(String name, int age) {
// Method body
}
}
ExpressionParser+StandardEvaluationContext+ParameterNameDiscoverer
一般我们在使用Spring EL
表达式的时候我们都会结合这三个类来使用。
比如我们写一个EL表达式的工具类
/**
* el表达式解析
*
* @param expressionString 解析值
* @param method 方法
* @param args 参数
* @return
*/
public static Object parse(String expressionString, Method method, Object[] args) {
if (DataUtils.isEmpty(expressionString)) {
return null;
}
//获取被拦截方法参数名列表
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] paramNameArr = discoverer.getParameterNames(method);
//SPEL解析
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < Objects.requireNonNull(paramNameArr).length; i++) {
context.setVariable(paramNameArr[i], args[i]);
}
return parser.parseExpression(expressionString).getValue(context);
}
我们通过LocalVariableTableParameterNameDiscoverer
解析方法的参数名字
然后将方法名和参数方法StandardEvaluationContext
上下文中,然后通过EL
解析器SpelExpressionParser
去获取value
Spring cache 中EL表达式中的使用
实际Spring cache中也实现了自己的EL表达式解析
我们可以简单看看。核心实现类是CacheOperationExpressionEvaluator
CacheOperationExpressionEvaluator
继承了一个抽象类CachedExpressionEvaluator
然后CachedExpressionEvaluator
中就有具体的EL表达式的一些功能实现,包括我们之前提到的参数解析器ParameterNameDiscoverer
,然后就是EL
表达式解析的核心实现类SpelExpressionParser
其实我们可以看到EL
表达式的组合拳三者已显其二,还有一个EvaluationContext
在哪呢?
实际是在方法generateKey
中去创建的。我们来看看源码
可以看到在这里就创建了一个EvaluationContext
,这里的上下文也是Cache模块自己实现一个类CacheEvaluationContext
具体放了个啥呢?实际就放了一个返回结果
总结
本次我们详细的分析了Spring EL
表达式中SpelExpressionParser
的一些核心用法。以及实际使用中是如何结合ParameterNameDiscoverer
和EvaluationContext
去使用的。
简单实现了一个EL
表达式的工具类,也结合Spring cache
模块的EL
表达式进行了简单的源码分析。
让大家对Spring EL
表达式有全面的了解
测试源码地址
- 源码:https://github.com/weihubeats/weihubeats_demos/blob/master/spring-boot-demos/spring-boot-base/src/test/java/SpringTest.java