标题
- 前言
- 概述
- 功能
- 使用
- 字面值
- 对象属性和方法
- 变量引用
- #this 和 #root变量
- 获取类的类型
- 调用对象(类)的方法
- 调用类构造器
- 类型转换
- 运算符
- 赋值运算符
- 条件(关系)表达式
- 三元表达式
- Elvis 操作符
- 逻辑运算
- instanceof 和 正则表达式的匹配操作符
- 安全导航操作员
- 数组集合(Array 、List、Map)
- 构造列表,map
- StandardEvaluationContext与SimpleEvaluationContext的区别
- 针对集合的应用
- 集合筛选
- 集合投影
- 表达式模板
- rootObject
- SpelParserConfiguration
- EvaluationContext
- Bean引用
- 案例
- Maven依赖环境
- 算术计算
- 公式计算
- 数组集合类处理
- 数组
- 数组元素修改
- List集合
- 对List集合元素进行修改
- Map集合
- map修改
- 将上下文对象转换为Map对象
前言
关于SpEL表达式写法是很灵活的,不要拘泥表面,但是前提是你需要知道他有哪些功能,支持哪些语法,然后就可以花里胡哨的表演了。这块建议顺序看,越靠前的越要先懂,后面要用到前置知识的!还有一点就是,他虽然可以那么玩,但是很多时候我们可以通过程序转换的就不要SpEL表达式来进行实现。就是说不要过度使用SpEL,让他做一些简单的事,也侧面反应了SpEL的强大,只有你想不到的没他干不了的。
概述
SpEL:(Spring Expression Language) 是一种表达式语言,是一种强大,简洁的装配Bean的方式。他可以通过运行期间执行的表达式将值装配到我们的属性或构造函数当中,也可以调用JDK中提供的静态常量,获取外部Properties文件中的的配置。(能在运行时构建复杂表达式、存取对象图属性、对象方法调用等等,并且能与Spring功能完美整合,如能用来配置Bean定义。)
表达式语言给静态Java语言增加了动态功能。
SpEL是单独模块,只依赖于core模块,不依赖于其他模块,可以单独使用。
功能
SpEL支持各种操作和函数,包括算术运算、逻辑运算、条件判断、正则表达式匹配、集合操作等。它还支持访问上下文中的变量和参数,以及调用对象的方法。
使用
任何语言都需要有自己的语法,SpEL当然也不例外。所以我们应该能够想到,给一个字符串最终解析成一个值,这中间至少得经历:
字符串 ===> 语法分析 ===> 生成表达式对象 ===> (添加执行上下文) ===> 执行此表达式对象 ===> 返回结果
关于SpEL的几个概念:
- 表达式(“干什么”):SpEL的核心,所以表达式语言都是围绕表达式进行的。
- 解析器(“谁来干”):用于将字符串表达式解析为表达式对象。
- 上下文(“在哪干”):表达式对象执行的环境,该环境可能定义变量、定义自定义函数、提供类型转换等等。
- root根对象及活动上下文对象(“对谁干”):root根对象是默认的活动上下文对象,活动上下文对象表示了当前表达式操作的对象。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyUser {
private String name;
private int age;
}
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.ArrayList;
import java.util.HashMap;
public class SpelTest {
public static void main(String[] args) {
// 1.创建表达式解析器
ExpressionParser parser = new SpelExpressionParser();
// 2.创建变量上下文,设置变量
StandardEvaluationContext ctx = new StandardEvaluationContext();
//把list和map都放进环境变量里面去
ctx.setVariable("myPerson", new MyUser("fsx", 30));
ctx.setVariable("myList", new ArrayList<String>() {{
add("fsx");
add("周杰伦");
}});
ctx.setVariable("myMap", new HashMap<String, Integer>(2) {{
put("fsx", 18);
put("周杰伦", 40);
}});
// 3.根据表达式计算结果
// MyUser {name='fsx', age=30}
System.out.println(parser.parseExpression("#myPerson").getValue(ctx));
// fsx
System.out.println(parser.parseExpression("#myPerson.name").getValue(ctx));
// 安全导航运算符 "?",安全导航操作符是用来避免'NullPointerException`
System.out.println(parser.parseExpression("#myPerson?.name").getValue(ctx));
// setVariable方式取值不能像root一样,前缀不可省略~~显然找不到这个key就返回null呗~~~
System.out.println(parser.parseExpression("#name").getValue(ctx));
// [fsx, 周杰伦]
System.out.println(parser.parseExpression("#myList").getValue(ctx));
// 周杰伦
System.out.println(parser.parseExpression("#myList[1]").getValue(ctx));
// 请注意对Map取值两者的区别:中文作为key必须用''包起来 当然['fsx']也是没有问题的
// 18
System.out.println(parser.parseExpression("#myMap[fsx]").getValue(ctx));
// 40
System.out.println(parser.parseExpression("#myMap['周杰伦']").getValue(ctx));
// =========若采用#key引用的变量不存在,返回的是null,并不会报错哦==============
System.out.println(parser.parseExpression("#map").getValue(ctx));
// 黑科技:SpEL内直接可以使用new方式创建实例 能创建数组、List、对象
//[Ljava.lang.String;@5f2108b5
System.out.println(parser.parseExpression("new String[]{'java','spring'}").getValue());
//[java, c语言, PHP]
System.out.println(parser.parseExpression("{'java','c语言','PHP'}").getValue());
// 静态方法方法调用 T(类的全路径限定名).方法名(), 不要使用${}包裹。此方法一般用来引用常量或静态方法
System.out.println(parser.parseExpression("T(java.lang.Math).random()").getValue(String.class));
// 实例方法调用
System.out.println(parser.parseExpression("#myPerson.getName()").getValue(ctx));
/**
* 调用Bean 中的方法
* 如果解析上下文已经配置,那么bean解析器能够 从表达式使用(@)符号查找bean类。
*/
// 此处用DefaultListableBeanFactory做测试,系统运行时可传入ApplicationContext
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("user", new MyUser("成龙", 30));
ctx.setBeanResolver(new BeanFactoryResolver(beanFactory));
// 3. spel解析器执行表达式取得结果
System.out.println(parser.parseExpression("@user.getName()").getValue(ctx, String.class));
}
}
字面值
这块没啥好讲的,基础的基础部分必须会
//Hello, World!
String result = parser.parseExpression("'Hello, ' + 'World!'").getValue(String.class);
SpEL支持的字面量包括:字符串、数字类型(int、long、float、double)、布尔类型、null类型。
class aa{
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
String str1 = parser.parseExpression("'Hello World!'").getValue(String.class);//Hello World!
//String str12 = parser.parseExpression("name").getValue(String.class);//报错哦,必须要引号
int int1 = parser.parseExpression("1").getValue(Integer.class);//1
int result = parser.parseExpression("2 + 3 * 2").getValue(Integer.class);//8
long long1 = parser.parseExpression("-1L").getValue(long.class);//-1
float float1 = parser.parseExpression("1.1").getValue(Float.class);//1.1
double double1 = parser.parseExpression("1.1E+2").getValue(double.class);//110.0
int hex1 = parser.parseExpression("0xa").getValue(Integer.class);//10
long hex2 = parser.parseExpression("0xaL").getValue(long.class);//10
boolean true1 = parser.parseExpression("true").getValue(boolean.class);//true
boolean false1 = parser.parseExpression("false").getValue(boolean.class);//false
Object null1 = parser.parseExpression("null").getValue(Object.class);//null
}
}
对象属性和方法
这块挺有意思的,务必会,本质就是我们既可以通过属性名也可以通过方法名来获取值,底层无非就是反射得到的结果
注意!属性名的第一个字母不区分大小写。
@Data
public class Dog {
private String name;
private Friend friend;
private Integer[] prices;
private List<String> ls;
}
@Data
public class Friend {
private String name;
public Friend(String name) {
this.name = name;
}
}
public class TestOne {
public static void main(String[] args) {
property();
}
public static void property(){
ExpressionParser parser = new SpelExpressionParser();
Dog dog = new Dog();
dog.setName("yq");
dog.setFriend(new Friend("yl"));
// 通过属性:yq
StandardEvaluationContext context = new StandardEvaluationContext(dog);
String dogName1 = parser.parseExpression("name").getValue(context, String.class);
String dogName1N = parser.parseExpression("Name").getValue(context, String.class);
String dogName2 = parser.parseExpression("name").getValue(dog, String.class);
String dogName2N = parser.parseExpression("Name").getValue(dog, String.class);
String dogName3 = parser.parseExpression("getName()").getValue(dog, String.class);
//通过属性:yq1900
String dogName1900 = parser.parseExpression("name + 1900").getValue(dog, String.class);
//通过方法:yq1900
String getDogName = (String)parser.parseExpression("getName() + 1900").getValue(dog);
//通过属性:yl
String friendName = (String) parser.parseExpression("friend.name").getValue(dog);
//通过属性方法混合:yl
String getFriendName1 = (String) parser.parseExpression("getFriend().getName()").getValue(dog);
String getFriendName2 = (String) parser.parseExpression("friend.getName()").getValue(dog);
String getFriendName3 = (String) parser.parseExpression("getFriend().name").getValue(dog);
//yl123
String getFriendName = (String) parser.parseExpression("getFriend().name+123").getValue(dog);
}
}
如果你要是用#
的形式访问,需要setVariable
context.setVariable("dog", dog);
String dogName11 = parser.parseExpression("#dog.name").getValue(context, String.class);
System.out.println("dogName11:"+dogName11);//dogName11:yq
String dogName22 = parser.parseExpression("#dog.getName()").getValue(context, String.class);
System.out.println("dogName22:"+dogName22);//dogName22:yq
String dogName11N = parser.parseExpression("#dog.Name").getValue(context, String.class);
System.out.println("dogName11N:"+dogName11N);//dogName11N:yq
变量引用
这块没啥好说的啊,基础,setVariable这种记得用#
取
public class Peo {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("name", "Alice");
context.setVariable("age", 25);
//Alice is 25 years old
String greeting = parser.parseExpression("#name + ' is ' + #age + ' years old'").getValue(context, String.class);
}
}
#this 和 #root变量
#this
变量引用当前的评估对象(根据该评估对象解析非限定引用)。
#root
变量总是被定义并引用根上下文对象。虽然#this可能会随着表达式的组成部分的计算而变化,但是#root总是指根。
这块root开始真理解不了,后来大概了解了
public class Peo {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// 正常创建StandardEvaluationContext实例
StandardEvaluationContext context = new StandardEvaluationContext();
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 30));
people.add(new Person("Charlie", 20));
context.setVariable("people", people);
// 使用 #this 筛选年龄大于 20 岁的人(基于当前正在处理的元素进行判断)
List<Person> result1 = (List<Person>) parser.parseExpression("#people.?[#this.age > 20]")
.getValue(context);
System.out.println("使用 #this 筛选后的人员列表: "+result1);//[Person(name=Alice, age=25), Person(name=Bob, age=30)]
// 使用 #root 结合正确的获取变量方式筛选年龄大于 20 岁的人(从根上下文对象获取 people 列表进行判断) 下面这个无论我怎么改写都报错,开始理解不了,下面理解了
// List<Person> result2 = (List<Person>) parser.parseExpression("#root.#people.?[#root.#people['age'] > 20]") .getValue(context);
List<Person> result2 = (List<Person>) parser.parseExpression("#root.#people.?[#this.age > 20]").getValue(context);
System.out.println("使用 #root 筛选后的人员列表: " + result2);//使用 #root 筛选后的人员列表: [Person(name=Alice, age=25), Person(name=Bob, age=30)]
}
}
@Data
@AllArgsConstructor
class Person {
private String name;
private int age;
}
#root
真正玩法
ExpressionParser parser = new SpelExpressionParser();
// 正常创建StandardEvaluationContext实例
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 30));
people.add(new Person("Charlie", 20));
StandardEvaluationContext context = new StandardEvaluationContext(people);
Object value = parser.parseExpression("#root").getValue(context);
//原始人员列表: [Person(name=Alice, age=25), Person(name=Bob, age=30), Person(name=Charlie, age=20)]
System.out.println("原始人员列表: " + value);
Object value1 = parser.parseExpression("#root[0]").getValue(context);
//第一个人员: 第一个人员: Person(name=Alice, age=25)
System.out.println("第一个人员: " + value1);
// 使用 #root 结合正确的获取变量方式筛选年龄大于 20 岁的人(从根上下文对象获取 people 列表进行判断)
List<Person> result2 = (List<Person>) parser.parseExpression("#root.?[#this.age > 20]").getValue(context);
//使用 #root 筛选后的人员列表: [Person(name=Alice, age=25), Person(name=Bob, age=30)]
System.out.println("使用 #root 筛选后的人员列表: " + result2);
下面我再次尝试root用法,跟我想象中的写法不一样
public class Peo {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(4000.0));
employees.add(new Employee(6000.0));
employees.add(new Employee(2000.0));
Department department = new Department(employees);
StandardEvaluationContext context = new StandardEvaluationContext(department);
// 计算部门平均工资 "#root.employees" 等价 "employees"
double averageSalary = ((List<Employee>) parser.parseExpression("employees")
.getValue(context)).stream().mapToDouble(Employee::getSalary).average().orElse(0.0);
System.out.println("部门平均工资: " + averageSalary);//部门平均工资: 4000.0
// 将平均工资作为变量存储在上下文中
context.setVariable("averageSalary", averageSalary);
Object expression = parser.parseExpression("employees").getValue(context);
//[Employee(salary=4000.0), Employee(salary=6000.0), Employee(salary=2000.0)]
System.out.println(expression);
Object value = parser.parseExpression("#averageSalary").getValue(context);
//"#root#averageSalary" 等价 "#averageSalary" 4000.0
System.out.println(value);
List<Employee> result2 = (List<Employee>) parser.parseExpression("employees.?[#this.salary > #averageSalary]")
.getValue(context);
System.out.println("工资高于部门平均工资的员工数量: " + result2.size());//工资高于部门平均工资的员工数量: 1
}
}
@Data
@AllArgsConstructor
class Employee {
private double salary;
}
@Data
@AllArgsConstructor
class Department {
private List<Employee> employees;
}
获取类的类型
这块是必须要知道的
可以使用特殊的T
运算符来指定java.lang.Class的实例(类型
)。静态方法也是通过使用这个操作符来调用的。
StandardEvaluationContext使用TypeLocator来查找类型,StandardTypeLocator(可以替换)是基于对java.lang包的理解而构建的。所以java.lang
中类型的T()
引用不需要使用全限定名,但是其他包中的类,必须使用全限定名。
public class Peo {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
//class java.util.Date
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
//class java.lang.String
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
//class com.example.Animal
Class stringClass2 = parser.parseExpression("T(com.example.Animal)").getValue(Class.class);
//CEILING = 2 FLOOR=3 true
boolean trueValue = parser.parseExpression(
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
.getValue(Boolean.class);
//调用类中静态方法测试
String value = parser.parseExpression(
"T(com.example.Animal).getText('调用类中静态方法')")
.getValue(String.class);
}
}
class Animal {
public static String getText(String text) {
return text + "测试";
}
}
调用对象(类)的方法
还可以调用对象的方法,有些强
class Calculator {
public int add(int a, int b) {
return a + b;
}
public static void main(String[] args) {
Calculator calculator = new Calculator();
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext(calculator);
int result = parser.parseExpression("add(10, 20)").getValue(context, Integer.class);//30
// 调用String类的substring方法 cde
String bc = parser.parseExpression("'abcdef'.substring(2, 5)").getValue(String.class);
}
}
调用类静态方法
public class Peo {
public static void main(String[] args) throws NoSuchMethodException {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// 获取要调用的方法
context.setVariable("strToUpperCase",
Peo.class.getDeclaredMethod("strToUpperCase", String.class));
// HELLO
String helloWorldReversed = parser.parseExpression(
"#strToUpperCase('hello')").getValue(context, String.class);
}
// 准备一个要调用的目标方法
public static String strToUpperCase(String input) {
return input.toUpperCase();
}
}
调用类构造器
使用new运算符调用构造函数。除了基本类型(int、float等)和String之外,所有类型都应该使用完全限定的类名。
public class Peo {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
// 使用new运算符调用Person类的构造函数创建对象
Person person = (Person) parser.parseExpression("new com.example.Person('Alice', 25)")
.getValue(context);
//创建的人员姓名: Alice, 年龄: 25
System.out.println("创建的人员姓名: " + person.getName() + ", 年龄: " + person.getAge());
// 使用new运算符调用ArrayList的构造函数创建空的ArrayList对象
//下面这个写法报错 Expression [new java.util.ArrayList<String>()] @23: EL1050E: The arguments (...) for the constructor call are missing
//ArrayList<String> list = (ArrayList<String>) parser.parseExpression("new java.util.ArrayList<String>()").getValue(context);
List<String> list = (List<String>) parser.parseExpression("new java.util.ArrayList()").getValue(context, List.class);
list.add("元素1");
list.add("元素2");
System.out.println("创建的ArrayList包含元素: " + list);//创建的ArrayList包含元素: [元素1, 元素2]
// 使用new运算符调用Date类的构造函数创建表示当前时间的Date对象
Date date = (Date) parser.parseExpression("new java.util.Date()") .getValue(context);
System.out.println("创建的Date对象表示的时间: " + date);//创建的Date对象表示的时间: Fri Dec 27 14:20:21 CST 2024
}
}
@Data
@AllArgsConstructor
class Person {
private String name;
private int age;
}
类型转换
public class Peo {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("numberStr","123");
int number = parser.parseExpression("T(Integer).parseInt(#numberStr)").getValue(context, Integer.class);
// 创建一个表示当前时间的Date对象
Date currentDate = new Date();
context.setVariable("currentDate", currentDate);
// 定义日期格式化的格式字符串,这里设置为"yyyy-MM-dd HH:mm:ss",表示年-月-日 时:分:秒
String formatStr = "yyyy-MM-dd HH:mm:ss";
context.setVariable("formatStr", formatStr);
// 使用SpEL表达式将Date对象转换为指定格式的字符串
// 使用SpEL表达式将Date对象转换为指定格式的字符串
String dateStr = parser.parseExpression(
"new java.text.SimpleDateFormat(#formatStr).format(#currentDate)")
.getValue(context, String.class);
System.out.println("转换后的日期字符串为: " + dateStr);//转换后的日期字符串为: 2024-12-31 14:45:25
}
}
运算符
- 假设有一个用户类User,我们需要根据用户的某些属性动态计算某个值,可以使用SpEL表达式如下:
// 假设已经获取到ExpressionParser和StandardEvaluationContext实例
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
User user = new User();
user.setName("John");
user.setAge(30);
context.setVariable("user", user);
// 使用SpEL表达式计算年龄是否大于25
String expression = "#user.age > 25";
Boolean result = parser.parseExpression(expression).getValue(context, Boolean.class);
System.out.println(result); // 输出 true
赋值运算符
若要给对象设置属性,请使用赋值运算符(=)。这通常在对setValue的调用中完成,但也可以在对getValue的调用中完成。
public class Peo {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
//这块 context都对,我文章中也提到过两者区别,注释掉的功能更丰富强大但性能低根据不同场景切换即可
//StandardEvaluationContext context = new StandardEvaluationContext();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
Inventor inventor = new Inventor();
parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic");
System.out.println(inventor.getName()); // Aleksandar Seovic
// 或者这样赋值
String aleks = parser.parseExpression(
"Name = 'Aleksandar Seovic2'").getValue(context, inventor, String.class);
System.out.println(inventor.getName()); // Aleksandar Seovic2
}
}
@Data
class Inventor{
private String name;
}
条件(关系)表达式
SpEL还提供 等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=),区间(between)运算
,还有除(/
),取模(%
),取反(!
)
SpEL同样提供了等价的“EQ” 、“NE”、 “GT”、“GE”、 “LT” 、“LE”来表示等于、不等于、大于、大于等于、小于、小于等于,DIV(/)、MOD(%) 、NOT(!)
不区分大小写。
null不被视为任何东西(即不为零)。因此,任何其他值总是大于null (X > null总是为真),并且没有任何其他值小于零(X < null总是为假)。
在 SpEL 中,这种比较被定义为:任何非null的值都被认为大于null,并且没有值被认为小于null。
import lombok.Data;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class Peo {
public static void main(String[] args) {
Person person = new Person();
person.setName("John Doe");
person.setAge(30);
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext(person);
//true
boolean isAdult = parser.parseExpression("age >= 18").getValue(context, Boolean.class);
//Hello, adult
String greeting = parser.parseExpression("age >= 18 ? 'Hello, adult' : 'Hello, child'").getValue(context, String.class);
//正确
String istrue = parser.parseExpression("5 EQ 5 ? '正确' : '错误'").getValue(context, String.class);
// true 这块 boolean Boolean 结果都一样
boolean between1 = parser.parseExpression("1 between {1,2}").getValue(boolean.class);
// 比较一个正数和null true
boolean result1 = parser.parseExpression("5 > null").getValue( Boolean.class);
// 比较一个负数和null true
boolean result2 = parser.parseExpression("-3 > null").getValue(Boolean.class);
// 尝试比较一个值小于null(根据规则应该总是为假) false
boolean result3 = parser.parseExpression("2 < null").getValue( Boolean.class);
boolean result4 = parser.parseExpression("null < null").getValue( Boolean.class);//false
boolean result5 = parser.parseExpression("null > null").getValue( Boolean.class);//false
boolean result7 = parser.parseExpression("null != null").getValue( Boolean.class);//false
boolean result6 = parser.parseExpression("null == null").getValue( Boolean.class);//true
}
}
@Data
class Person {
private String name;
private int age;
}
三元表达式
public class Peo {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
Student student = new Student(20,70.0, 22);
context.setVariable("student", student);
//成年人
String result = parser.parseExpression("#student.age >= 18? '成年人' : '未成年人'")
.getValue(context,String.class);
//都满足
String result2 = parser.parseExpression("#student.score >= 60 && #student.attendance >= 20?'都满足':'不满足'")
.getValue(context, String.class);
}
}
@Data
@AllArgsConstructor
class Student{
private Integer age;
private Double score;
private Integer attendance;
}
Elvis 操作符
在三元运算符语法中,你通常要将一个变量重复两次 -> (name != null ? name : "Unknown"
)改进 elvis -> (name?:'Unknown')
public class Peo {
//Elvis运算符是三元运算符语法的缩写,用于Groovy语言中。
public static void main(String[] args) throws NoSuchMethodException {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
/**
* 处理可能为 null 的字符串属性
* 假设有一个 Person 类,包含 nickname(昵称)属性,有时候这个属性可能没有被赋值(即为 null),
* 我们希望在获取这个属性值用于展示等情况时,如果是 null 就使用一个默认值来代替,就可以使用 Elvis 操作符。
*/
// 创建一个Person对象,设置昵称
Person person1 = new Person("小机灵");
// 使用Elvis操作符获取昵称,如果昵称不为null就返回昵称本身,为null则返回"暂无昵称"
String displayNickname1 = parser.parseExpression("nickname?:'暂无昵称'")
.getValue(context, person1, String.class);
System.out.println("人员1的昵称: " + displayNickname1);//人员1的昵称: 小机灵
// 创建另一个Person对象,不设置昵称(默认为null)
Person person2 = new Person(null);
// 同样使用Elvis操作符获取昵称
String displayNickname2 = parser.parseExpression("nickname?:'暂无昵称'")
.getValue(context, person2, String.class);
System.out.println("人员2的昵称: " + displayNickname2);//人员2的昵称: 暂无昵称
List<Student> students = new ArrayList<>();
students.add(new Student(80));
students.add(new Student(null));
students.add(new Student(90));
context.setVariable("students", students);
// 使用Elvis操作符将学生成绩列表中为null的成绩替换为0后,计算平均成绩
// 修正后的表达式,完整写出Elvis操作符对应的三元表达式形式
Double averageGrade2 = ((List<Integer>) parser.parseExpression("#students.![grade!=null?grade:0]")
.getValue(context, students, List.class)).stream().mapToDouble(Integer::doubleValue).average().orElse(0.0);
System.out.println("学生平均成绩: " + averageGrade2);
}
}
@Data
@AllArgsConstructor
class Person {
private String nickname;
}
@Data
@AllArgsConstructor
class Student {
private Integer grade;
}
逻辑运算
SpEL支持以下逻辑运算符:and、or、not
这块不难理解看看就行
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class Peo {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
//下面要调用方法所以这块调用了有参构造传入对象
StandardEvaluationContext context = new StandardEvaluationContext(new Peo());
// 结果: false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
// 调用方法并根据方法返回值判断 false
String expression1 = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue1 = parser.parseExpression(expression1).getValue(context, Boolean.class);
// -- OR -- true
boolean trueValue2 = parser.parseExpression("true or false").getValue(Boolean.class);
// 调用方法并根据方法返回值判断 true
String expression3 = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue3 = parser.parseExpression(expression3).getValue(context, Boolean.class);
// -- NOT -- 取反 false
boolean falseValue4 = parser.parseExpression("!true").getValue(Boolean.class);
// 调用方法并根据方法返回值判断 false
String expression5 = "!isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue5 = parser.parseExpression(expression5).getValue(context, Boolean.class);
System.out.println(trueValue5);
// -- AND and NOT -- true
String expression6 = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue6 = parser.parseExpression(expression6).getValue(context, Boolean.class);
}
public static boolean isMember(String name){
if(name.equals("Nikola Tesla")){
return true;
}
if(name.equals("Albert Einstein")){
return false;
}
return false;
}
}
instanceof 和 正则表达式的匹配操作符
使用基本类型时要小心,因为它们会立即被装箱为包装器类型,所以1 instanceof T(int)会计算为false,而1 instanceof T(Integer)会计算为true。秉承一切皆对象,平时推荐大家使用基本数据类型的包装类,避免不必要的踩坑
- instanceof
这块注释掉的写法是报错的,我不理解为什么会报错,所以就把所有类setVariable换了种写法
根据它的报错信息EL1007E: Property or field 'com' cannot be found on null
,它压根就没解析为一个路径,经过后来研究,类全限定名你需要用T()
包起来,是我蠢了,与君共勉
package com.example;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class Peo {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
Cat cat = new Cat();
context.setVariable("pet", cat);
context.setVariable("cat", com.example.Cat.class);
context.setVariable("dog", com.example.Dog.class);
context.setVariable("animal", com.example.Animal.class);
boolean falseValue1 = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
boolean falseValue2 = parser.parseExpression(
"'1' instanceof T(Integer)").getValue(Boolean.class);
boolean trueValue1 = parser.parseExpression(
"'1' instanceof T(String)").getValue(Boolean.class);
boolean trueValue2 = parser.parseExpression(
"1 instanceof T(Integer)").getValue(Boolean.class);
// 检查pet是否是Cat类型的实例 true
// boolean result1 = parser.parseExpression("#pet instanceof com.example.Cat").getValue(context, Boolean.class);
boolean result1 = parser.parseExpression("#pet instanceof #cat").getValue(context, Boolean.class);
boolean result11 = parser.parseExpression("#pet instanceof T(com.example.Cat)").getValue(context, Boolean.class);
System.out.println("pet是否是Cat类型的实例: " + result1);
// 检查pet是否是Animal类型的实例 false
// boolean result2 = parser.parseExpression("#pet instanceof com.example.Animal").getValue(context, Boolean.class);
boolean result2 = parser.parseExpression("#pet instanceof #dog").getValue(context, Boolean.class);
System.out.println("pet是否是Animal类型的实例: " + result2);
// 检查pet是否是Dog类型的实例 true
// boolean result3 = parser.parseExpression("#pet instanceof com.example.Dog").getValue(context, Boolean.class);
boolean result3 = parser.parseExpression("#pet instanceof #animal").getValue(context, Boolean.class);
System.out.println("pet是否是Dog类型的实例: " + result3);
}
}
class Animal {}
class Cat extends Animal {}
class Dog extends Animal {}
- 正则表达式
SpEL 也支持使用正则表达式,其中对应的关键字为match
。
假设我们要检查一个字符串是否是有效的电子邮件地址格式(这只是一个简单示例,实际的电子邮件验证更复杂)。
这块没啥好说的,注意写法就OK了
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class Peo {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
String email = "test@example.com";
context.setVariable("emailAddress", email);
// context.setVariable("emailRegex", "'^[a-zA-Z0-9]+[.+-]+[a-zA-Z0-9]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9.-]+$'");
context.setVariable("emailRegex", "^[a-zA-Z]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
// 使用正则表达式检查是否是有效的电子邮件格式
boolean result1 = parser.parseExpression("#emailAddress matches '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+'").getValue(context, Boolean.class);
boolean result2 = parser.parseExpression("#emailAddress matches #emailRegex").getValue(context, Boolean.class);
boolean result3 = parser.parseExpression("'9184@qq.com' matches #emailRegex").getValue(context, Boolean.class);
}
}
安全导航操作员
安全导航操作符用于避免NullPointerException,来自Groovy语言。通常,当引用一个对象时,可能需要在访问该对象的方法或属性之前验证它不为null。为了避免这种情况,安全导航运算符返回null,而不是引发异常。
在 Spring 表达式语言(SpEL)中,安全导航运算符(Safe Navigation Operator)用于避免空指针异常的情况,它的语法形式是在属性访问时使用 ?. 来替代常规的 .,当对象为 null 时,整个表达式求值不会抛出空指针异常,而是返回 null。
@Data
@AllArgsConstructor
class Address {
private String city;
}
@Data
@AllArgsConstructor
class Person {
private Address address;
}
class SpELSafeNavigationOperatorExample1 {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// 创建一个有地址的Person对象
Address address = new Address("Beijing");
Person person1 = new Person(address);
// 使用安全导航运算符获取城市名称
String cityName1 = parser.parseExpression("address?.city")
.getValue(context, person1, String.class);
System.out.println("人员1所在城市: " + cityName1);//人员1所在城市: Beijing
// 创建一个没有地址(地址为null)的Person对象
Person person2 = new Person(null);
// 使用安全导航运算符获取城市名称,此时不会抛出空指针异常,而是返回null
String cityName2 = parser.parseExpression("address?.city")
.getValue(context, person2, String.class);
System.out.println("人员2所在城市: " + cityName2);//人员2所在城市: null
}
}
在集合元素的属性访问中使用
下面这块我不理解,明明注释掉的写法挺正确,但是就报错,我服了,望指正
@Data
@AllArgsConstructor
class Book {
private String title;
}
@Data
@AllArgsConstructor
class Student {
private List<Book> books;
}
class SpELSafeNavigationOperatorExample2 {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// 创建一个有书籍的Student对象
Book book1 = new Book("Math Book");
List<Book> books = new ArrayList<>();
books.add(book1);
Student student1 = new Student(books);
// 使用安全导航运算符获取第一本书的书名
// 下面这个写法报错 不理解 Expression [books?.[0].title] @5: EL1049E: Unexpected data after '.': 'lsquare([)'
// String bookTitle12 = parser.parseExpression("books?.[0].title")
// .getValue(context, student1, String.class);
// 使用正确语法的嵌套表达式先判断books是否为null,再获取第一本书的书名
String bookTitle1 = parser.parseExpression("(books!=null?books[0].title:null)")
.getValue(context, student1, String.class);
System.out.println("学生1的第一本书名: " + bookTitle1);
// 创建一个没有书籍(书籍列表为null)的Student对象
Student student2 = new Student(null);
// 使用安全导航运算符获取第一本书的书名,不会抛出空指针异常,返回null
// String bookTitle2 = parser.parseExpression("books?.[0].title")
String bookTitle2 = parser.parseExpression("(books!=null?books[0].title:null)")
.getValue(context, student2, String.class);
System.out.println("学生2的第一本书名: " + bookTitle2);
// 创建一个有书籍但第一本书为null的Student对象
Student student3 = new Student(new ArrayList<>());
student3.getBooks().add(null);
// 使用安全导航运算符获取第一本书的书名,同样不会抛出空指针异常,返回null ,下面写法又报错服了
// String bookTitle3 = parser.parseExpression("books?.[0].title")
String bookTitle3 = parser.parseExpression("(books!=null?books[0]!=null?books[0].title:null:null)")
.getValue(context, student3, String.class);
System.out.println("学生3的第一本书名: " + bookTitle3);
}
}
数组集合(Array 、List、Map)
这块必须掌握,这里描述的简单这块建议多看我下面案例模块的,那块代码案例多,加深理解
数组和 list 都是通过 [下标获取]
map 的内容是通过在括号内指定字面的 key 值来获得的
public class CollectTest {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Dog dog = new Dog();
dog.setPrices(new Integer[]{1,2,3});
dog.setLs(Arrays.asList("pz", "yq", "yl", "xx"));
Map<String, String> build = new HashMap<>();
build.put("f", "pz");
build.put("s", "yq");
build.put("t", "yl");
dog.setLsMap( build );
//2
Integer invention1 = parser.parseExpression("prices[1]").getValue(
context, dog, Integer.class);
Integer invention2 = parser.parseExpression("getPrices[1]").getValue(
context, dog, Integer.class);
//yq
String invention3 = parser.parseExpression("ls[1]").getValue(context, dog, String.class);
String invention4 = parser.parseExpression("getLs[1]").getValue(context, dog, String.class);
//yq
String value1 = parser.parseExpression("lsMap['s']").getValue(dog, String.class);
String value2 = parser.parseExpression("getLsMap['s']").getValue(dog, String.class);
}
}
/**
* #numbers:引用上下文中的 numbers 集合变量。
* .?:表示这是一个集合选择操作。
* [#this > 8]:定义了筛选条件,#this 关键字引用当前遍历到的集合元素,表达式 #this > 8 用于检查每个元素是否大于 8。
*/
List<Integer> numbers = Arrays.asList(5, 10, 3, 8, 12);
context.setVariable("numbers", numbers);
// 使用选择表达式筛选出大于8的元素
List<Integer> filteredList = (List<Integer>) parser.parseExpression("#numbers.?[#this > 8]")
.getValue(context, List.class);
System.out.println("大于8的元素: " + filteredList);//大于8的元素: [10, 12]
构造列表,map
在SpEL中可以使用{e1,e2,e3}
的形式来构造一个List
如果我们希望构造的List的元素还是一个List,则可以将构造的List的元素定义为{e1,e2,e3}
这样的形式,如{ {1,2},{3,4,5},{6,7,8,9} }
。如果需要构造一个空的List
,则直接将对应的表达式字符串定义为{}
即可。
我们知道Map是可以key-value的形式存在的,在SpEL中如果我们需要构造一个Map则可以使用{key1:value1,key2:value2}
这样的形式进行定义,即使用大括号包起来,然后key和value之间以冒号:分隔构成一个Entry,多个Entry之间以逗号分隔。如果需要构造一个空的Map
,则只需指定对应的表达式为{:}
即可。
对于数组的构造就比较简单了,我们可以在表达式中使用Java代码中new的语法来构造一个数组。如:new int[]{1,2,3}
如果需要构造一个空数组,则可以直接new一个空的数组。多维数组也是支持的,但是多维数组只支持定义一个空的数组,对于需要初始化指定数组元素的定义暂时在SpEl中是不支持的。
这块就是快速构建一个集合,一般来说测试时候用。正常情况下我们是已经有集合了,再说也没必须要这样构建集合,了解即可
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
//内联列表:[1, 2, 3, 4]
List<Integer> value = (List)parser.parseExpression("{1,2,3,4}").getValue();
System.out.println(value);
//内联Map: {name=Nikola, dob=10-July-1856}
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue();
System.out.println(inventorInfo);
// {name={first=Nikola, last=Tesla}, dob={day=10, month=July, year=1856}}
Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue();
System.out.println(mapOfMaps);
//Array 构造: [0, 0, 0, 0]
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue();
System.out.println(numbers1);
System.out.println(Arrays.toString(numbers1));
// Array with initializer [1, 2, 3]
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue();
System.out.println(Arrays.toString(numbers2));
// Multi dimensional array,二维数组不能初始化
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue();
System.out.println(Arrays.toString(numbers3[0]));//[0, 0, 0, 0, 0]
System.out.println(Arrays.toString(numbers3[1]));//[0, 0, 0, 0, 0]
System.out.println(Arrays.toString(numbers3[3]));//[0, 0, 0, 0, 0]
}
StandardEvaluationContext与SimpleEvaluationContext的区别
StandardEvaluationContext
完整的上下文功能
SimpleEvaluationContext
精简版的上下文,去除了Java类型参照、构造器、Bean参照等功能
-
功能丰富度
StandardEvaluationContext :这是一个功能强大且完整的求值上下文。它支持 SpEL 语言的全部特性,包括属性访问、方法调用、构造函数调用、变量绑定、类型转换等多种复杂操作。可以在其中设置变量、注册自定义函数等多种自定义操作,为表达式求值提供了丰富的上下文环境。
SimpleEvaluationContext :它是一种简化的求值上下文。主要用于只读数据绑定场景,对 SpEL 的功能进行了限制。它不支持一些复杂的操作,如方法调用(除了在特定的白名单中的方法)、构造函数调用、变量绑定等。这样的限制使得它在安全性和性能方面有一定的优势,特别是在只需要进行简单的属性访问的场景下。 -
性能和安全性
StandardEvaluationContext :由于其功能全面,支持的操作繁多,在内部实现上相对复杂。这可能会导致在一些简单场景下性能不如SimpleEvaluationContext。同时,因为它支持更多可能修改系统状态的操作(如方法调用、变量绑定等),如果使用不当,可能会带来安全风险,例如恶意构造的表达式可能会调用不期望的方法或者修改系统状态。
SimpleEvaluationContext :通过限制功能,其内部实现相对简单,在性能上有一定优势,特别是在只需要进行简单属性访问的大规模数据处理场景下。并且由于限制了可能有风险的操作,在安全性方面更有保障,确保表达式求值不会产生意外的副作用。
// 明确初始化成绩数组,确保有值
double[] scoresArray = {80.0, 90.0, 75.0};
Student student = new Student("Alice", scoresArray, null, null);
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext(student);
// EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
/**
* 在你提供的代码中,虽然使用StandardEvaluationContext和SimpleEvaluationContext都得到了相同的结果,
* 但这是因为表达式scoresArray[1]只是简单地访问了数组中的一个元素(属性访问)。
* 如果表达式变得更复杂,比如需要调用Student类的方法或者绑定变量,那么SimpleEvaluationContext可能就无法满足需求,会抛出异常。
* 例如,如果表达式是#student.getName().toUpperCase()(假设Student类有getName方法),
* 使用SimpleEvaluationContext就会因为不支持方法调用而无法正确求值,而StandardEvaluationContext可以正确处理这种情况。
*/
Double getTwo = parser.parseExpression("scoresArray[1]").getValue(
context, student, Double.class);
System.out.println(getTwo);
针对集合的应用
class testA{
public static void main(String[] args) {
getListValue();
}
/**
* 获取满足条件的集合的值
* .?[ ] 结构就是在 SpEL 中实现基于特定条件对集合进行筛选
* ,以获取符合期望条件的元素子集的一种语法手段。
*/
private static void getListValue() {
Account account = new Account("Deniro");
ExpressionParser parser
= new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext(account);
//将数据转换成集合
List<Double> scores = new ArrayList<>();
scores.addAll(Arrays.asList(23.1, 82.3, 55.9));
context.setVariable("scores", scores);//在上下文中定义 scores 变量
List<Double> scoresGreat80 = (List<Double>) parser.parseExpression("#scores.?[#this>80]")
.getValue(context);
//[82.3]
System.out.println(scoresGreat80);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Account {
private String name;
}
}
集合筛选
在 Spring 表达式语言(SpEL)中,.?[selectionExpression]
是一种用于集合筛选的语法结构。它应用于集合类型(如List、Set等),可以从集合中筛选出满足selectionExpression条件的元素,并返回一个新的集合,这个新集合包含了原始集合中符合条件的元素。
@Data
@AllArgsConstructor
class Person {
private String name;
private int age;
}
class SpELCollectionFilterExample {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 18));
people.add(new Person("Charlie", 22));
context.setVariable("people", people);
// 使用.?[selectionExpression]语法筛选年龄大于20岁的人
List<Person> filteredPeople = (List<Person>) parser.parseExpression("#people.?[age > 20]")
.getValue(context);
System.out.println("年龄大于20岁的人员列表:"+filteredPeople);//[Person(name=Alice, age=25), Person(name=Charlie, age=22)]
}
}
在上述代码中,#people.?[age > 20]
这个表达式就是使用了集合筛选语法。#people
是在上下文中定义的人员列表变量,.?[age > 20]
部分是筛选条件,age > 20
就是selectionExpression
,它表示从#people
列表中筛选出年龄大于 20 岁的Person
对象。最后将筛选后的结果存储在filteredPeople列表中并打印输出。
selectionExpression的细节
- 引用当前元素:在selectionExpression中,可以通过#this来引用当前正在被遍历和判断的集合元素。例如,如果要筛选出名字长度大于 3 的人员,表达式可以写成#people.?[#this.name.length() > 3]。这里#this就代表了people列表中的每一个Person对象,通过#this.name.length()获取每个人员姓名的长度来进行条件判断。
- 复杂条件组合:selectionExpression可以包含复杂的逻辑表达式。比如,要筛选出年龄大于 20 岁并且名字以A开头的人员,可以使用表达式#people.?[age > 20 && #this.name.startsWith(‘A’)]。在这里,使用了逻辑与(&&)操作符来组合两个条件,只有同时满足年龄大于 20 岁和名字以A开头这两个条件的人员才会被筛选出来。
- 访问嵌套属性和方法:如果集合元素是复杂对象,其中包含其他对象或者有多层嵌套的属性和方法,也可以在selectionExpression中进行访问。例如,假设Person类中有一个Address类型的属性,Address类中有一个city属性,要筛选出居住在某个城市(比如New York)的人员,可以这样写表达式:#people.?[address.city == ‘New York’](前提是Person类中有getAddress方法来获取Address对象)。这展示了可以在筛选表达式中深入访问集合元素的嵌套属性来构建复杂的筛选条件。
集合投影
基本概念
在 Spring 表达式语言(SpEL)中,集合投影(Collection Projection)是一种操作,它允许你从集合中的每个元素提取特定的属性或执行一个操作,并将结果收集到一个新的集合中。这种操作可以方便地对集合中的数据进行转换和提取。
语法形式
集合投影的语法一般是 collection.![projectionExpression]
。其中,collection 是要进行投影操作的集合对象(例如一个 List、Set 等),![projectionExpression] 是投影表达式,用于指定从集合元素中提取或计算的内容。
@Data
@AllArgsConstructor
class Person {
private String name;
private int age;
}
class SpELCollectionProjectionExample {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 18));
people.add(new Person("Charlie", 22));
context.setVariable("people", people);
// 使用集合投影语法提取人员姓名列表
List<String> names = (List<String>) parser.parseExpression("#people.![name]").getValue(context);
System.out.println("人员姓名列表: " + names);// [Alice, Bob, Charlie]
}
}
表达式模板
基本概念
Spring 表达式语言(SpEL)中的表达式模板提供了一种在文本块中嵌入 SpEL 表达式的方式。它允许你将静态文本和动态的 SpEL 表达式组合在一起,就像在一个模板中填充动态内容一样。这种方式在生成动态消息、构建 SQL 查询语句、配置文件中的动态属性设置等场景非常有用。
语法形式
表达式模板使用#{}来包裹 SpEL 表达式。在解析时,SpEL 会先计算#{}中的表达式,然后将结果替换到模板中相应的位置,最终得到一个完整的字符串或者其他类型的值(取决于模板的使用场景)。
注意事项
转义字符:在表达式模板中,如果需要在#{}外面使用#{或}这些字符,可能需要进行转义,以避免语法错误。具体的转义规则可能因使用场景和解析器的配置而有所不同。
表达式求值顺序:在复杂的表达式模板中,要注意 SpEL 表达式的求值顺序可能会影响最终结果。如果有多个#{}表达式,它们会按照在模板中的出现顺序依次求值并替换。
上下文变量的可见性:确保在表达式模板中使用的上下文变量(通过context.setVariable等方式设置的变量)在解析表达式时是可见的,并且变量的类型和属性符合表达式中的引用要求,否则可能会出现求值错误。
@Data
@AllArgsConstructor
class User {
private String username;
private String email;
public String getUsername() {
return username;
}
public String getEmail() {
return email;
}
}
class SpELExpressionTemplateExample {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
User user = new User("'JohnDoe'", "'johndoe@example.com'");
// StandardEvaluationContext context = new StandardEvaluationContext(user);
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("user", user); // 设置变量到上下文中
// 使用SpEL表达式 TODO 这个写法报错,理解不了为何报错
// Expression [亲爱的 #{user.username} 欢迎您注册我们的平台 您的注册邮箱是 #{user.email} 请妥善保管 ] @4: EL1041E: After parsing a valid expression, there is still more data in the expression: 'hash(#)'
String welcomeEmailContent = parser.parseExpression("亲爱的 #{user.username} 欢迎您注册我们的平台 您的注册邮箱是 #{user.email} 请妥善保管 ")
.getValue(context, String.class);
String welcomeEmailContent2 = "亲爱的 " + parser.parseExpression("#user.username").getValue(context, String.class)
+ " 欢迎您注册我们的平台 您的注册邮箱是 "
+ parser.parseExpression("#user.email").getValue(context, String.class)
+ " 请妥善保管";
// 使用纯SpEL表达式
String welcomeEmailContent3 = parser.parseExpression(
"T(java.lang.String).format('亲爱的 %s 欢迎您注册我们的平台 您的注册邮箱是 %s 请妥善保管', #user.username, #user.email)")
.getValue(context, String.class);
System.out.println(welcomeEmailContent);
System.out.println(welcomeEmailContent2);
System.out.println(welcomeEmailContent3);
}
}
经过我的尝试你要想通过#{}来实现占位符替换 模板写法为#{[待替换字符]}
我们还要引入模板TemplateParserContext
,以及最后的数据值封装为Map
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
User user = new User("JohnDoe", "johndoe@example.com");
String message="\"亲爱的 #{[userName]},欢迎您注册我们的平台!您的注册邮箱是 #{[email]},请妥善保管。\"";
TemplateParserContext templateParserContext=new TemplateParserContext();
Expression expression = parser.parseExpression(message,templateParserContext);
//模拟数据
Map<String,Object> map=new HashMap<>();
map.put("userName",user.getUsername());
map.put("email","johndoe@example.com");
String value = expression.getValue(map, String.class);
System.out.println(value);//"亲爱的 JohnDoe,欢迎您注册我们的平台!您的注册邮箱是 johndoe@example.com,请妥善保管。"
}
实现二:表达式模板 TemplateParserContext
public static void main(String[] args) {
//创建解析器
SpelExpressionParser parser = new SpelExpressionParser();
//创建解析器上下文
ParserContext context = new TemplateParserContext("%{", "}");
Expression expression = parser.parseExpression("你好:%{#name},我们正在学习:%{#lesson}", context);
//创建表达式计算上下文
EvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.setVariable("name", "路人甲java");
evaluationContext.setVariable("lesson", "spring高手系列!");
//获取值
String value = expression.getValue(evaluationContext, String.class);
System.out.println(value);//你好:路人甲java,我们正在学习:spring高手系列!
}
其他玩法
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
String expressionStr = "Random number : #{T(java.lang.Math).random() * 100}";
String result = parser.parseExpression(expressionStr, new TemplateParserContext()).getValue(String.class);
System.out.println(result);//Random number : 随机数字
expressionStr = "the year is #{T(java.util.Calendar).getInstance().get(T(java.util.Calendar).YEAR)}";
Expression expression = parser.parseExpression(expressionStr, new TemplateParserContext());
System.out.println(expression.getValue());//the year is 2024
expressionStr = "这是一个公司信息详情信息页,公司名称:#{[companyName]},公司法人:#{[legalPerson]},公司电话:#{[companyTel]},公司地址:#{[companyAddress]}";
TemplateParserContext templateParserContext = new TemplateParserContext();
expression = parser.parseExpression(expressionStr, templateParserContext);
//模拟数据
Map<String, String> map = new HashMap<>();
map.put("companyName", "特斯拉(上海)有限公司");
map.put("legalPerson", "马斯克");
map.put("companyTel", "123456");
map.put("companyAddress", "中国(上海)自由贸易试验区临港新片区江山路5000号");
String value = expression.getValue(map, String.class);
//这是一个公司信息详情信息页,公司名称:特斯拉(上海)有限公司,公司法人:马斯克,公司电话:123456,公司地址:中国(上海)自由贸易试验区临港新片区江山路5000号
System.out.println(value);
}
rootObject
rootObject
是在StandardEvaluationContext
(标准评估上下文)中设置的一个对象,它是 SpEL 表达式求值的根对象。当 SpEL 表达式中没有明确指定从哪个对象获取属性或调用方法时,默认就会从rootObject开始查找和操作。
@Data
@AllArgsConstructor
public class Person {
private String name;
private int age;
public String introduce() {
return "我叫" + name + ",今年" + age + "岁。";
}
}
class SpelRootObjectExample {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Person person = new Person("张三", 25);
/**
* 当通过parser.parseExpression("name").getValue(context, String.class)获取name属性值时,
* 由于没有指定从哪个对象获取,SpEL 就会从rootObject(即person对象)中查找name属性并返回其值。
* 同理,parser.parseExpression("introduce()").getValue(context, String.class)
* 会调用rootObject(person对象)的introduce方法并返回结果。
*/
// 设置rootObject
StandardEvaluationContext context = new StandardEvaluationContext(person);
// 直接通过属性名获取rootObject的属性值
String name = parser.parseExpression("name").getValue(context, String.class);
System.out.println("姓名:" + name);//姓名:张三
// 调用rootObject的方法
String introduction = parser.parseExpression("introduce()").getValue(context, String.class);
System.out.println(introduction);//我叫张三,今年25岁。
}
}
SpelParserConfiguration
SpelParserConfiguration在 Spring Expression Language(SpEL)中是一个用于配置 SpEL 解析器行为的类
配置解析器的行为细节:例如是否允许数组或集合元素的自动增长、是否启用编译器等。这些配置可以影响 SpEL 表达式的解析和求值过程,以满足不同的应用场景和需求。
自动增长数组和集合:
默认行为:在默认情况下,当尝试向一个已满的数组或集合中添加元素时,SpEL 会抛出异常。
配置自动增长:可以通过SpelParserConfiguration来配置允许数组或集合自动增长。
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.ArrayList;
import java.util.List;
public class SpelParserConfigurationExample {
public static void main(String[] args) {
// 配置允许集合自动增长
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
SpelExpressionParser parser = new SpelExpressionParser(config);
List<String> list = new ArrayList<>();
list.add("item1");
list.add("item2");
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("myList", list);
// 向已满的集合中添加元素,由于配置了自动增长,不会抛出异常
parser.parseExpression("#myList.add('item3')").getValue(context);
System.out.println(context.getVariable("myList"));
}
}
在上述示例中,通过SpelParserConfiguration(true, true)
创建了一个配置对象,其中两个true参数分别表示允许数组自动增长和允许集合自动增长。然后将此配置传递给SpelExpressionParser,这样在后续的表达式求值中,当向已满的集合添加元素时,集合会自动增长而不会报错。
EvaluationContext
EvaluationContext
是 SpEL 表达式求值时的上下文
,它包含了表达式中可能引用到的变量、对象、函数、类型等信息,为表达式的求值提供了一个运行时环境。
提供变量和对象访问:可以将需要在表达式中使用的变量或对象设置到EvaluationContext中,表达式在求值时就能从上下文中获取这些变量或对象的值或引用,进行相应的操作,如获取对象的属性、调用对象的方法等。
支持类型引用和函数注册:可以在上下文中注册自定义类型和函数,使得表达式能够使用这些自定义的类型和函数进行求值。例如,可以注册一个自定义的工具函数,然后在表达式中调用该函数进行特定的计算或处理。
StandardEvaluationContext:这是 SpEL 中最常用的EvaluationContext实现类。
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class SpelEvaluationContextExample {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
// 设置变量
context.setVariable("myVar", "Hello, SpEL!");
// 解析表达式并求值
String result = parser.parseExpression("#myVar").getValue(context, String.class);
System.out.println(result);
}
}
使用注意事项
对象引用和生命周期管理:当将对象设置到EvaluationContext中时,需要注意对象的引用和生命周期。如果对象在上下文中被引用,但在外部被修改或销毁,可能会导致表达式求值出现意外结果。
上下文的线程安全性:在多线程环境下使用EvaluationContext时,需要确保其线程安全性。如果多个线程同时修改或访问同一个EvaluationContext,可能会导致数据不一致或其他并发问题。可以根据具体情况考虑使用线程安全的EvaluationContext实现类或采取适当的同步措施。
Bean引用
如果已经用bean解析器配置了评估上下文,则可以使用@
符号从表达式中查找bean。
要访问工厂bean本身,应该在bean名称前加上&
符号:
这块博主还没深入研究,乏了,等后续用到再看吧
- 引入相关依赖(以 Maven 项目为例,假设使用 Spring Boot 方便搭建环境,你也可以根据实际情况调整为普通 Spring 项目依赖配置)
在 pom.xml 文件中添加以下依赖:
<dependencies>
<!-- Spring Boot 核心依赖,包含了 Spring 相关基础功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.14</version>
</dependency>
<!-- Spring 表达式语言依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.23</version>
</dependency>
</dependencies>
- 创建普通 Bean 和工厂 Bean 示例类
import org.springframework.stereotype.Component;
@Component
public class UserService {
private String serviceName = "User Service";
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public void printUserInfo() {
System.out.println("执行用户服务相关操作,服务名称: " + serviceName);
}
}
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;
@Component
public class UserServiceFactoryBean implements FactoryBean<UserService> {
@Override
public UserService getObject() throws Exception {
UserService userService = new UserService();
userService.setServiceName("Custom User Service from Factory");
return userService;
}
@Override
public Class<?> getObjectType() {
return UserService.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
在上述代码中:
UserService 是一个普通的 Spring Bean,提供了一些简单的业务方法(这里只是简单打印服务名称用于示例)。
UserServiceFactoryBean 是一个实现了 FactoryBean 接口的工厂 Bean,它负责创建 UserService 的实例,并且可以在创建过程中对实例进行一些自定义的初始化操作,比如修改服务名称等。
- 自定义 MyBeanResolver(用于演示自定义解析 Bean 的逻辑)
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.HashMap;
import java.util.Map;
public class MyBeanResolver {
private Map<String, Object> beanMap = new HashMap<>();
public MyBeanResolver() {
// 模拟创建并放入一些Bean实例到自定义的解析器中
UserService userService = new UserService();
beanMap.put("userService", userService);
try {
UserServiceFactoryBean factoryBean = new UserServiceFactoryBean();
beanMap.put("userServiceFactoryBean", factoryBean);
beanMap.put("&userServiceFactoryBean", factoryBean); // 同时放入工厂 Bean 本身引用
} catch (Exception e) {
e.printStackTrace();
}
}
public Object resolve(EvaluationContext context, String beanName) throws AccessException {
return beanMap.get(beanName);
}
}
这里自定义的 MyBeanResolver 通过一个 Map 来模拟存储了 UserService 这个普通 Bean 和 UserServiceFactoryBean 这个工厂 Bean 及其本身的引用(以 & 开头的键值对形式存储),在 resolve 方法中根据传入的 Bean 名称从 Map 里获取对应的 Bean 实例返回。
- 使用 MyBeanResolver 进行 Bean 引用示例(包含普通 Bean 和工厂 Bean 引用)
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class SpELBeanReferenceExampleWithMyResolver {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
// 设置自定义的Bean解析器
context.setBeanResolver(new MyBeanResolver());
// 通过 @ 符号引用普通 Bean 并调用其方法
Object userServiceBean = parser.parseExpression("@userService")
.getValue(context);
if (userServiceBean instanceof UserService) {
((UserService) userServiceBean).printUserInfo();
}
// 通过 & 符号引用工厂 Bean 本身
Object factoryBean = parser.parseExpression("&userServiceFactoryBean")
.getValue(context);
if (factoryBean instanceof UserServiceFactoryBean) {
try {
UserService userServiceFromFactory = ((UserServiceFactoryBean) factoryBean).getObject();
userServiceFromFactory.printUserInfo();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
在这个示例中:
首先创建了 ExpressionParser 和 StandardEvaluationContext,然后设置了自定义的 MyBeanResolver。
通过 parser.parseExpression(“@userService”).getValue(context); 使用 @ 符号引用了名为 userService 的普通 Bean,并调用其 printUserInfo 方法展示可以正常操作该普通 Bean。
通过 parser.parseExpression(“&userServiceFactoryBean”).getValue(context); 使用 & 符号引用了名为 userServiceFactoryBean 的工厂 Bean 本身,获取到工厂 Bean 实例后,调用其 getObject 方法获取由工厂创建的 UserService 实例,再调用该实例的 printUserInfo 方法,展示了如何访问工厂 Bean 本身以及通过它获取创建的 Bean 实例进行操作。
- 使用 BeanFactoryResolver 进行 Bean 引用示例(同样包含普通 Bean 和工厂 Bean 引用,基于 Spring 应用上下文)
以下是基于 Spring 应用上下文使用 BeanFactoryResolver 的示例,这里通过创建一个简单的 Spring 配置类来模拟获取应用上下文(实际应用中通常是通过启动 Spring 容器等方式获取完整的应用上下文):
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.BeanFactoryResolver;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
@Bean
public UserServiceFactoryBean userServiceFactoryBean() {
return new UserServiceFactoryBean();
}
}
public class SpELBeanReferenceExampleWithBeanFactoryResolver {
public static void main(String[] args) {
// 创建Spring应用上下文
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
// 设置BeanFactoryResolver,传入应用上下文
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
// 通过 @ 符号引用普通 Bean 并调用其方法
Object userServiceBean = parser.parseExpression("@userService")
.getValue(context);
if (userServiceBean instanceof UserService) {
//执行用户服务相关操作,服务名称: User Service
((UserService) userServiceBean).printUserInfo();
}
// 通过 & 符号引用工厂 Bean 本身
Object factoryBean = parser.parseExpression("&userServiceFactoryBean")
.getValue(context);
if (factoryBean instanceof UserServiceFactoryBean) {
try {
UserService userServiceFromFactory = ((UserServiceFactoryBean) factoryBean).getObject();
userServiceFromFactory.printUserInfo();
//执行用户服务相关操作,服务名称: Custom User Service from Factory
} catch (Exception e) {
e.printStackTrace();
}
}
// 关闭应用上下文(释放资源等)
applicationContext.close();
}
}
在这个示例里:
先是通过 @Configuration 注解定义了 AppConfig 这个 Spring 配置类,在里面使用 @Bean 注解定义了 UserService 这个普通 Bean 和 UserServiceFactoryBean 这个工厂 Bean,让 Spring 容器可以管理它们的创建和生命周期。
然后在 main 方法中创建了 AnnotationConfigApplicationContext 作为 Spring 的应用上下文,基于配置类来初始化相关的 Bean 实例。
接着创建 ExpressionParser 和 StandardEvaluationContext,并设置 BeanFactoryResolver 传入应用上下文,使得后续 SpEL 表达式能基于 Spring 的 Bean 管理机制进行操作。
之后同样通过 @ 符号引用普通 Bean 和通过 & 符号引用工厂 Bean 本身,并进行相应的操作展示,最后关闭应用上下文释放资源。
通过以上示例,你可以清晰地看到在不同的 Bean 解析器配置下(自定义的 MyBeanResolver 和系统自带的 BeanFactoryResolver ),如何在 SpEL 中通过 @ 符号引用普通 Bean 以及通过 & 符号引用工厂 Bean 本身来进行相应的操作,这在实际基于 Spring 的项目开发中,对于灵活地在表达式中操作各种 Bean 实例很有帮助。你可以根据实际业务场景进一步扩展和调整这些示例代码,以满足具体的需求。
案例
你要是案例中的语法看不懂,上面使用篇幅都有对应的内容讲解
以下是我在jdk17下进行的测试代码案例
在 spring-context 包中已经引入 spring-expression 包, 在其他非Spring的项目中,可以单独引入:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.0.0.RELEASE</version>
<scope>compile</scope>
</dependency>
Maven依赖环境
我这块是一个SpringBoot项目,我把所有依赖都拿过来了,你按需引入即可
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<dependency>
<groupId>com.belerweb</groupId>
<artifactId>pinyin4j</artifactId>
<version>2.5.0</version>
</dependency>
<!-- <dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>-->
<!-- PostgreSQL -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.13</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.13.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.2-jre</version>
</dependency>
</dependencies>
算术计算
算术运算:SpEL 支持的算术运算可以是加、减、乘、除、求余、指数等
这块就是你已经得到了具体数值,想进行算术运算得到运行结果 2行代码就能搞定
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
//当表达式需要引用外部变量、对象属性或者执行方法调用等更复杂的操作时,就需要使用StandardEvaluationContext。这块不涉及可省略
//StandardEvaluationContext context = new StandardEvaluationContext();
//double result = parser.parseExpression("(80.0 + 90.0 + 75.0) / 3").getValue(context, Double.class);
//直接解析并计算表达式 (也可以BigDecimal类型哦)
double result = parser.parseExpression("(80.0 + 90.0 + 75.0)/3").getValue(Double.class);
// 使用DecimalFormat进行格式化,保留两位小数
DecimalFormat decimalFormat = new DecimalFormat("#.##");
String formattedResult = decimalFormat.format(result);
System.out.println("计算结果为: " + formattedResult);//计算结果为: 81.67
/**
* 升级理解 StandardEvaluationContext的用途案例
* 通过context.setVariable("x", 20.0)将变量x的值设置为20.0,然后在表达式#x + 10中通过#x引用这个变量,最后计算得到结果30.0。
* 所以StandardEvaluationContext提供了一种在表达式中使用外部变量和对象的机制,使SpEL的功能更加强大。
*/
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("x", 20.0);
double result2 = parser.parseExpression("#x + 10").getValue(context, Double.class);
System.out.println("计算结果为: " + result2);//计算结果为: 30.0
}
public class New {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
System.out.println((parser.parseExpression("(1+2)*5 + 8-6/2")).getValue());//加减乘除 20
System.out.println((parser.parseExpression("8%3").getValue()));//求余 2
System.out.println((parser.parseExpression("2.0e3").getValue()));//指数 2000.0
System.out.println((parser.parseExpression("2^3").getValue()));//指数 8
}
}
公式计算
在实际业务当中,假如我们商品价格不是固定不变的,我们通过中台系统配置会配置对应商品的一个价格计算公式,后续希望根据定义好的公式去计算对应商品价格。
import lombok.Data;
@Data
public class Product {
private String name;
private double price;
private double discountRate;
private String formula;
}
import org.springframework.stereotype.Service;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@Service
public class PricingService {
public double calculateFinalPrice(Product product) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext(product);
// 计算最终价格,原价 * (1 - 折扣率)
// return parser.parseExpression("price * (1 - discountRate)").getValue(context, Double.class);
return parser.parseExpression(product.getFormula()).getValue(context, Double.class);
}
public static void main(String[] args) {
Product product = new Product();
product.setPrice(50);
product.setDiscountRate(0.5);
product.setFormula("price * (1 - discountRate)");
PricingService pricingService = new PricingService();
System.out.println(pricingService.calculateFinalPrice(product));//25.0
}
}
数组集合类处理
假设我们正在开发一个学生管理系统,需要处理学生的成绩信息。成绩可能以不同的集合形式存储,并且需要根据一定的规则进行查询和计算。我们将使用 SpEL 来操作数组、列表(List)和映射(Map)这些集合类型。
@Data
@AllArgsConstructor
public class Student {
private String name;
private double[] scoresArray;
private List<Double> scoresList;
private Map<String, Double> subjectScoresMap;
}
数组
注意啊 这块什么时候要用#
整明白了
在Spring的SpEL中,使用 # 前缀来引用上下文中定义的变量。当您在 StandardEvaluationContext 中通过 setVariable
方法设置一个变量时,您可以在SpEL表达式中使用 #
前缀来访问这个变量。
Student 类中,scoresArray 是一个属性,而不是上下文中的变量。
在SpEL表达式中直接使用 变量名来访问这个属性,而不是使用 # 前缀。这是因为 scoresArray 是 student 对象的一个字段,而不是在 EvaluationContext 中定义的变量。
如果您想要在SpEL表达式中使用 # 前缀来访问 scoresArray,您需要在 StandardEvaluationContext 中将其设置为一个变量,这就是为什么您需要调用 context.setVariable(“scoresArray”, student.getScoresArray());。这样,scoresArray 就成为了上下文中的一个变量,而不是 student 对象的一个属性。
下面是实现一个计算数组平均值的简单案例,其本质就是方括号符号[]
获得的
/**
* SpEL 操作数组(Array)
* 计算学生成绩数组的平均分:
* 通过#scoresArray[索引]的形式访问数组元素,然后计算数组中成绩的平均值。
*/
public static void main(String[] args) {
// 明确初始化成绩数组,确保有值
double[] scoresArray = {80.0, 90.0, 75.0};
Student student = new Student("Alice", scoresArray, null, null);
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext(student);
//这块我们关于数组的任意操作基本都有对应语法实现,简单写几个
Double getOne = parser.parseExpression("scoresArray[0]").getValue(
context, student, Double.class);
System.out.println(getOne);//80.0
Double getThree = parser.parseExpression("scoresArray.length").getValue(
context, student, Double.class);
System.out.println(getThree);//3.0
//这块我在提一嘴,如果你想判断数组中是否包含某个值,可以使用Arrays.asList转换为List后调用contains方法。这块目的是让你知道对应的写法
// 将double[]转换为Double[]
Double[] boxedArray = Arrays.stream(scoresArray).boxed().toArray(Double[]::new);
student.setBoxedArray(boxedArray);//必须设置这个新属性不然它是获取不到这个新数组的
// 利用Arrays.asList转换为List后调用contains方法
boolean containsValue = parser.parseExpression("T(java.util.Arrays).asList(boxedArray).contains(75.0)")
.getValue(context, student, Boolean.class);
System.out.println(containsValue);
// 修改表达式,通过循环计算数组元素总和,再除以数组长度来求平均值
String expressionStr = "0";
for (int i = 0; i < scoresArray.length; i++) {
expressionStr = "(" + expressionStr + " + scoresArray[" + i + "])";
}
expressionStr += " / scoresArray.length";
double averageScoreArray = parser.parseExpression(expressionStr).getValue(context, Double.class);
/*double averageScoreArray = parser.parseExpression("(((0 + scoresArray[0]) + scoresArray[1]) " +
"+ scoresArray[2]) / scoresArray.length").getValue(context, Double.class);*/
System.out.println("Average score of array: " + averageScoreArray);//Average score of array: 81.66666666666667
//现在 你要想#的形式获取就必须设置 setVariable ,但是我明明都包装到student里面了呀 暂时没理解
context.setVariable("scoresArray", student.getScoresArray());
Double getTwo = parser.parseExpression("#scoresArray[1]").getValue(
context, student, Double.class);
System.out.println(getTwo);//90.0
double averageScoreArray2 = parser.parseExpression("(((0 + #scoresArray[0]) + #scoresArray[1]) + #scoresArray[2]) / #scoresArray.length").getValue(context, Double.class);
System.out.println("Average score of array: " + averageScoreArray2);//Average score of array: 81.66666666666667
}
数组元素修改
本质就是
parser.parseExpression("#myArray[1] = 5").getValue(context)
public static void main(String[] args) {
// 创建数组
int[] numbers = {1, 2, 3};
// 创建表达式解析器
ExpressionParser parser = new SpelExpressionParser();
// 创建评估上下文,并将包含数组的对象设置进去(这里直接把数组本身设置进去,也可以是包含数组的自定义对象等情况)
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("myArray", numbers);
// 后续进行数组元素修改相关的表达式操作
// 解析表达式并修改数组中索引为1的元素值(将原本的2修改为5)
parser.parseExpression("#myArray[1] = 5").getValue(context);
// 验证修改结果,输出修改后的数组元素 修改后的数组:[1, 5, 3]
System.out.println("修改后的数组:"+Arrays.toString(numbers));
}
List集合
这块模拟下List直接取值跟List集合中在嵌套一个集合的取值,其本质就是方括号符号[]
获得的
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class SpELListExample {
/**
* 集合及嵌套集合的简单操作
*/
public static void main(String[] args) {
// 定义Parser,可以定义全局的parser
ExpressionParser parser = new SpelExpressionParser();
// 创建一个只读数据绑定的评估上下文(如果需要读写数据绑定可以用其他方式创建)
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// 初始化Tesla对象及相关数据
Tesla tesla = new Tesla();
tesla.setInventions(Arrays.asList("交流电", "特斯拉线圈", "无线通信", "X光机"));
// 初始化IEEE对象及相关数据
IEEE ieee = new IEEE();
Member member = new Member();
member.setName("John Doe");
member.setInventions(Arrays.asList("发明1", "发明2", "发明3", "发明4", "发明5", "发明6", "发明7", "发明8"));
List<Member> members = new ArrayList<Member>();
members.add(member);
ieee.setMembers(members);
// 将Tesla对象放入评估上下文
context.setVariable("tesla", tesla);
// 将IEEE对象放入评估上下文
context.setVariable("ieee", ieee);
// 取出tesla对象的inventions 第四个数据
String invention = parser.parseExpression("inventions[3]").getValue(
context, tesla, String.class);
//Tesla的第四个发明是: X光机
System.out.println("Tesla的第四个发明是: " + invention);
// 取出ieee对象的第一个Member的name属性 这块属性的首字母大小写是不影响的
String name = parser.parseExpression("Members[0].Name").getValue(
context, ieee, String.class);
//IEEE第一个成员的名字是: John Doe
System.out.println("IEEE第一个成员的名字是: " + name);
Object name2 = parser.parseExpression("Members[0]").getValue(
context, ieee);
//Members[0]是: Member(name=John Doe, inventions=[发明1, 发明2, 发明3, 发明4, 发明5, 发明6, 发明7, 发明8])
System.out.println("Members[0]是: " + name2.toString());
// 取出ieee对象的第一个Member中的第七个Inventions
String invention2 = parser.parseExpression("Members[0].Inventions[6]").getValue(
context, ieee, String.class);
// IEEE第一个成员的第七个发明是: 发明7
System.out.println("IEEE第一个成员的第七个发明是: " + invention2);
}
}
// 代表特斯拉相关信息的类
@Data
@ToString
class Tesla {
private List<String> inventions;
}
// 代表IEEE组织成员相关信息的类
@Data
@ToString
class Member {
private String name;
private List<String> inventions;
}
// 代表IEEE组织相关信息的类
@Data
@ToString
class IEEE {
private List<Member> members;
}
class Other{
/**
* SpEL 操作列表(List)
* 找出学生成绩列表中的最高分:
* 这里使用T()操作符来引用java.util.Collections类,然后调用max方法找出成绩列表中的最高分
*/
public static void main(String[] args) {
List<Double> scoresList = Arrays.asList(85.0, 92.0, 78.0);
Student student = new Student("Bob", null, scoresList, null);
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext(student);
double maxScoreList = parser.parseExpression("T(java.util.Collections).max(scoresList)").getValue(context, Double.class);
System.out.println("Max score of list: " + maxScoreList);//Max score of list: 92.0
}
}
对List集合元素进行修改
本质就是
parser.parseExpression(format, parserContext).setValue(context, tarVal)
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
@Slf4j
public class ListSpELTest {
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Account {
private String name;
}
@Data
@AllArgsConstructor
public static class Info {
private String url ;
}
@Data
@AllArgsConstructor
public static class User {
private Long id;
private String name;
private List infos;
}
public static void main(String[] args) {
spEL2FillList();
spEL2Map();
}
/**
* 这块比如我们有一个用户,它有多个收获地址url,现在我们对它的收获地址进行批量修改
*/
private static void spEL2FillList() {
ExpressionParser parser
= new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
//解析模板#{}
TemplateParserContext parserContext = new TemplateParserContext();
User user = new User(1L, "tom",
Lists.newArrayList(new Info("xxx"), new Info("yyy")));
//User(id=1, name=tom, infos=[Info(url=xxx), Info(url=yyy)])
System.out.println(user);
context.setVariable("user", user);
String value = "#{#user.infos[%d].url}";
String parseValue = StringUtils.substring(value, 0, value.indexOf("[%d]")) + "}";//#{#user.infos}
if (!parseValue.equals(value)) {
List arrObj =
parser.parseExpression(parseValue, parserContext).getValue(context, List.class);
if (!CollectionUtils.isEmpty(arrObj)) {
for (int i = 0; i < arrObj.size(); i++) {
String format = String.format(value, i);//#{#user.infos[0].url}
String oriVal = parser.parseExpression(format, parserContext)
.getValue(context, String.class);
log.info("原始集合的值:{}", oriVal);//原始集合的值:xxx
//业务操作,重写数据
String tarVal = oriVal + "-目标值";//xxx-目标值
parser.parseExpression(format, parserContext).setValue(context, tarVal);
}
}
}
//User(id=1, name=tom, infos=[Info(url=xxx-目标值), Info(url=yyy-目标值)])
System.out.println(user);
}
上面案例复杂了
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
//修改list元素值
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
EvaluationContext context1 = new StandardEvaluationContext();
context1.setVariable("collection", list);
parser.parseExpression("#collection[1]").setValue(context1, 4);
int result1 = parser.parseExpression("#collection[1]").getValue(context1, int.class);
System.out.println(result1);//4
System.out.println(list);// [1, 4]
}
Map集合
其本质是通过key
获得的
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
//2.测试字典
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable("map", map);
System.out.println(parser.parseExpression("#map").getValue(context2));//{a=1, b=2, c=3}
System.out.println(parser.parseExpression("#map['b']").getValue(context2));//2
Map<String, Integer> result2 = parser.parseExpression("#map.?[key!='a']").getValue(context2, Map.class);//{b=2, c=3}
result2.forEach((key, value) -> {
System.out.println(key + ":" + value);
});
System.out.println("------------");
List<Integer> result3 = parser.parseExpression("#map.?[key!='a'].![value+1]").getValue(context2, List.class);//[3, 4]
System.out.println(result3);
result3.forEach(System.out::println);
}
map修改
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
//修改map元素值
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable("map", map);
parser.parseExpression("#map['a']").setValue(context2, 4);
Integer result2 = parser.parseExpression("#map['a']").getValue(context2, int.class);
System.out.println(result2);//4
}
将上下文对象转换为Map对象
//完整代码在上面List部分哦
private static void spEL2Map() {
ExpressionParser parser
= new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
Account account = new Account("Deniro");
User user = new User(1L, "tom", null);
//设置上下文对象
context.setVariable("account", account);
context.setVariable("user", user);
//将上下文对象转换为Map对象,支持三目表达式#user?.infos
Map<String, Object> objectMap =
(Map) parser.parseExpression("{acName:#account.name,userUrls:#user.infos?.![#this]}")
.getValue(context);
//{acName=Deniro, userUrls=null}
System.out.println(objectMap);
}
}