大数跨境
0
0

玩转Spring表达式语言SpEL:在项目实践中与AOP的巧妙结合

玩转Spring表达式语言SpEL:在项目实践中与AOP的巧妙结合 Spring全家桶实战案例
2024-02-18
0
导读:深入探索Spring表达式语言SpEL:结合实际项目与AOP的巧妙运用

环境: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 {  // 访问系统属性  @Value("#{ systemProperties['user.region'] }")  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() { @Override public 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. 实战案例

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface 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用于查询和操作对象,提供了简洁、强大的表达方式。

完毕!!!

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读38
粉丝0
内容832