环境:Spring5.3.23
1. 简介
Spring 表达式语言(简称 "SpEL")是一种功能强大的表达式语言,支持在运行时查询和操作对象图。该语言的语法与 Unified EL 相似,但提供了更多的功能,其中最主要的是方法调用和基本的字符串模板功能。
虽然还有其他几种 Java 表达式语言,如Avaitor、OGNL、MVEL 和 JBoss EL 等,创建 Spring 表达式语言的目的是为 Spring 社区提供一种支持良好的表达式语言,可在 Spring 产品组合的所有产品中使用。虽然SpEL是Spring产品组合中表达式求值的基础,但它并不直接与Spring绑定,可以独立使用。
表达式语言支持以下功能:
字面量表达
布尔运算符和关系运算符
正则表达式
类表达
访问属性、数组、列表和地图
方法调用
关系运算符
调用构造函数
Bean引用
数组构造
内联列表
模板表达式
以上只是部分,还支持其它情况,具体可以查看官方文档。本篇文章也不会对每一种都进行讲解,我们只对常用的一些做讲解,最后会结合一个实际的案例讲解。
示例:
ExpressionParser parser = new SpelExpressionParser();// 拼接字符串Expression exp = parser.parseExpression("'Hello'.concat('!')");String message = (String) exp.getValue();
输出结果
Hello!
2. 执行上下文
EvaluationContext 接口用于评估表达式以解析属性、方法或字段,并帮助执行类型转换。Spring 提供了两种实现。简单说我要执行一个表达式就需要在一个容器中。
SimpleEvaluationContext:针对不需要完全使用SpEL语言语法并且应该受到有意义限制的表达式类别,公开基本的SpEL语言功能和配置选项的子集。
StandardEvaluationContext:公开全部的SpEL语言特性和配置选项。你可以使用它来指定一个默认的根对象,并配置每个可用的求值相关策略。
SimpleEvaluationContext被设计为仅支持SpEL语言语法的一个子集。它不包括Java类型引用、构造函数和bean引用。你需要显式地选择对表达式中的属性和方法的支持级别。可以通过一个构建器来配置所需的确切支持级别,是以下内容中的一个或某些组合:
仅限自定义PropertyAccessor(无反射)
只读访问的数据绑定属性
读写数据绑定属性
示例:
static class Person {String name = "张三" ;}SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build() ;ExpressionParser parser = new SpelExpressionParser() ;Person root = new Person();parser.parseExpression("name").setValue(context, root, "李四") ;System.out.println(root.name) ;// 以上程序执行将会报错,因为构建的上下文是只读的,通过forReadWriteDataBinding则可以写入
SpelParserConfiguration
可以使用解析器配置对象(SpelParserConfiguration)来配置 SpEL 表达式解析器。配置对象可控制某些表达式组件的行为。例如,对数组或集合进行索引,而指定索引处的元素为空,SpEL 会自动创建该元素。这在使用由一连串属性引用组成的表达式时非常有用。对数组或列表进行索引,并指定一个超出数组或列表当前大小的索引,SpEL 可以自动增长数组或列表以容纳该索引。为了在指定的索引处添加元素,SpEL 将尝试使用元素类型的默认构造函数创建元素,然后再设置指定的值。如果元素类型没有默认构造函数,则会将空值添加到数组或列表中。如果没有知道如何设置值的内置或自定义转换器,空值将保留在数组或列表的指定索引处。下面的示例演示了如何自动增长列表,示例:
class Person {public List<String> list;}SpelParserConfiguration config = new SpelParserConfiguration(true, true) ;ExpressionParser parser = new SpelExpressionParser(config) ;Expression expression = parser.parseExpression("list[3]") ;Person rootObject = new Person();System.out.println(expression.getValue(rootObject)) ;System.out.println(rootObject.list.size() + ", " + rootObject.list) ;
控制台输出
4,
程序并没有报错,而是自动的给List集合中填充了元素(空字符串)。
3. 表达式定义
你可以将SpEL表达式与基于XML或基于注释的配置元数据一起用于定义BeanDefinition实例。在这两种情况下,定义表达式的语法形式都是
#{<expression string>} 。示例:
基于XML
<bean id="order" class="com.pack.Order"><property name="totalAmount" value="#{ T(java.lang.Math).random() * 1000.0 }"/></bean>
基于注解
public class Person {// 访问系统属性private String region;}
4. 表达式应用
4.1 集合访问及生成
List集合
class Person {public String name ;public Person(String name) {this.name = name ;}}class Demo {public List<Person> persons = new ArrayList<>() ;public Demo() {persons.add(new Person("张三")) ;persons.add(new Person("李四")) ;}}//StandardEvaluationContext context = new StandardEvaluationContext() ;context.setRootObject(new Demo()) ;SpelParserConfiguration config = new SpelParserConfiguration(true, true) ;ExpressionParser parser = new SpelExpressionParser(config) ;String name = parser.parseExpression("persons[0].name").getValue(context, String.class) ;// 输出:张三System.out.println(name) ;
Map集合
class Demo {public Map<String, Person> mappings = new HashMap<>() ;public Demo() {mappings.put("p1", new Person("张三")) ;mappings.put("p2", new Person("李四")) ;}}// ... 这里代码有上面一致String name = parser.parseExpression("mappings['p2'].name").getValue(context, String.class) ;// 输出:李四System.out.println(name) ;
Array数组
static class Demo {public Person[] ps = new Person[2] ;public Demo() {ps[0] = new Person("张三") ;ps[1] = new Person("李四") ;}}String name = parser.parseExpression("ps[1].name").getValue(context, String.class) ;// 输出:李四System.out.println(name) ;
生成List集合
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);// 输出:[1, 2, 3]System.out.println(numbers) ;
生成Map集合
Map person = (Map) parser.parseExpression("{name:'张三', age: '23'}").getValue(context);// 输出:{name=张三, age=23}System.out.println(person) ;
4.2 方法调用
StandardEvaluationContext context = new StandardEvaluationContext() ;// 设置根对象,这样就直接可以方法根对象上的方法或属性了context.setRootObject(new Person("莉莉")) ;SpelParserConfiguration config = new SpelParserConfiguration(true, true) ;ExpressionParser parser = new SpelExpressionParser(config) ;String value = parser.parseExpression("getName()").getValue(context, String.class) ;// 输出:莉莉System.out.println(value) ;// 输出:莉莉System.out.println(parser.parseExpression("name").getValue(context, String.class)) ;
4.3 运算符
关系运算符
// trueboolean ret = parser.parseExpression("2 == 2").getValue(Boolean.class);// falseret = parser.parseExpression("2 < -5.0").getValue(Boolean.class);// trueret = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
逻辑运算符
and(&&)or(||)not(!)
示例1
String expression = "1==2 or 1==1";boolean ret = parser.parseExpression(expression).getValue(context, Boolean.class);// 输出:trueSystem.out.println(ret) ;
示例2
static class Demo {public List<String> roles = new ArrayList<>() ;public Demo() {roles.add("ROLE_C") ;roles.add("ROLE_R") ;roles.add("ROLE_U") ;roles.add("ROLE_D") ;}public boolean hasAuthority(String role) {return roles.contains(role) ;}}StandardEvaluationContext context = new StandardEvaluationContext() ;context.setRootObject(new Demo()) ;ExpressionParser parser = new SpelExpressionParser() ;boolean ret = parser.parseExpression("hasAuthority('ROLE_C') and hasAuthority('ROLE_D')").getValue(context, Boolean.class) ;// 输出:trueSystem.out.println(ret) ;
数学运算符
ExpressionParser parser = new SpelExpressionParser() ;int ret = parser.parseExpression("8 / 5 % 2").getValue(Integer.class);// 输出:1System.out.println(ret) ;
4.4 访问变量
使用 #variableName 语法在表达式中引用变量。使用 EvaluationContext 实现中的 setVariable 方法可以设置变量。
StandardEvaluationContext context = new StandardEvaluationContext() ;context.setVariable("address", "新疆乌鲁木齐") ;ExpressionParser parser = new SpelExpressionParser() ;String address = parser.parseExpression("#address").getValue(context, String.class) ;// 输出:新疆乌鲁木齐System.out.println(address) ;
#this与#root变量
#this 变量始终是已定义的,它指的是当前的评估对象。#root 变量总是已定义,并指向根上下文对象。虽然 #this 可能会随着表达式组件的求值而变化,但 #root 总是指向根对象。下面的示例展示了如何使用 #this 和 #root 变量:
List<Integer> primes = new ArrayList<Integer>();primes.addAll(Arrays.asList(2,3,5,7,11,13,17));StandardEvaluationContext context = new StandardEvaluationContext() ;context.setVariable("primes", primes);ExpressionParser parser = new SpelExpressionParser() ;List<Integer> ret = (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context);// 输出:[11, 13, 17]System.out.println(ret) ;
4.5 方法
static class NameUtils {public static String reverseString(String input) {StringBuilder backwards = new StringBuilder(input.length()) ;for (int i = 0, leng = input.length(); i < leng; i++) {backwards.append(input.charAt(input.length() - 1 - i)) ;}return backwards.toString() ;}}StandardEvaluationContext context = new StandardEvaluationContext() ;context.setVariable("reverseString", NameUtils.class.getDeclaredMethod("reverseString", String.class)) ;ExpressionParser parser = new SpelExpressionParser() ;String ret = parser.parseExpression("#reverseString('pack')").getValue(context, String.class) ;// 输出:kcapSystem.out.println(ret) ;
4.6 Bean引用
如果当前上下文配置了 bean 解析器,则可以使用 @ 符号从表达式中查找 bean。
static class Person {}static class Student {}ExpressionParser parser = new SpelExpressionParser();StandardEvaluationContext context = new StandardEvaluationContext();context.setBeanResolver(new BeanResolver() {@Overridepublic Object resolve(EvaluationContext context, String beanName) throws AccessException {if (beanName.equals("person")) {return new Person() ;} else if (beanName.equals("student")) {return new Student() ;}return null ;}});Object bean = parser.parseExpression("@person").getValue(context) ;// 输出:com.pack.main.spel_parser.SpELBeanReferenceMain$Person@73a8dfccSystem.out.println(bean) ;// 输出:nullSystem.out.println(parser.parseExpression("&foo").getValue(context)) ;
以上是SpEL基础常用的一些表达式应用,接下来我们来看看在AOP中是如何使用SpEL的。
5. 实战案例
(ElementType.METHOD)(RetentionPolicy.RUNTIME)public AuthorityAuthentication {String value() default "" ;String id() default "" ;}class Teacher {private Long id ;public Teacher(Long id) {this.id = id;}}
切面类
@Aspectstatic class LogAspect {StandardEvaluationContext context = new StandardEvaluationContext() ;ExpressionParser parser = new SpelExpressionParser() ;ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();@Before("execution(* com.pack.main.aspect..*.*(..)) && @annotation(authority)")public void beforeSpel(JoinPoint point, AuthorityAuthentication authority) {System.out.printf("权限标识: %s, SpEL: %s%n", authority.value(), authority.id()) ;MethodSignature ms = (MethodSignature) point.getSignature() ;Object[] args = point.getArgs() ;// 获取执行方法的参数名称String[] names = parameterNameDiscoverer.getParameterNames(ms.getMethod()) ;for (int i = 0; i < names.length; i++) {// 将参数添加到上下文变量中context.setVariable(names[i], args[i]) ;}// 获取值Long id = parser.parseExpression(authority.id()).getValue(context, Long.class) ;System.out.printf("记录Teacher【%s】日志...%n", id);}}
该切面我们是可以完全可以通过args(teacher)方式来获取整个参数对象。不过我们这里是教你如何在切面中使用SpEL表达式,各位大佬可以发挥自己的想象。
Service类
class TeacherService {// 将id属性定义为SpEL表达式,获取参数的id值@AuthorityAuthentication(value = "ps:teacher:update", id="#teacher.id")public void operator(Teacher teacher) {System.out.println("教师修改成功...") ;}}
测试
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext ()) {context.registerBean(LogAspect.class) ;context.registerBean(TeacherService.class) ;context.refresh() ;TeacherService ts = context.getBean(TeacherService.class) ;ts.operator(new Teacher(666L)) ;}
输出
权限标识: ps:teacher:update, SpEL: #teacher.id记录Teacher【666】日志...教师修改成功...
总结:在Spring的SpEL中,“Evaluation”意为“评估”,指对表达式进行求值或评估的过程,以获得所需的结果或信息。SpEL用于查询和操作对象,提供了简洁、强大的表达方式。
完毕!!!



