当前位置: 首页 > news >正文

Spring SpEL表达式由浅入深

标题

  • 前言
  • 概述
  • 功能
  • 使用
    • 字面值
    • 对象属性和方法
    • 变量引用
    • #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));// fsxSystem.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']也是没有问题的// 18System.out.println(parser.parseExpression("#myMap[fsx]").getValue(ctx));// 40System.out.println(parser.parseExpression("#myMap['周杰伦']").getValue(ctx));// =========若采用#key引用的变量不存在,返回的是null,并不会报错哦==============System.out.println(parser.parseExpression("#map").getValue(ctx));// 黑科技:SpEL内直接可以使用new方式创建实例  能创建数组、List、对象//[Ljava.lang.String;@5f2108b5System.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做测试,系统运行时可传入ApplicationContextDefaultListableBeanFactory 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);//1int result = parser.parseExpression("2 + 3 * 2").getValue(Integer.class);//8long long1 = parser.parseExpression("-1L").getValue(long.class);//-1float float1 = parser.parseExpression("1.1").getValue(Float.class);//1.1double double1 = parser.parseExpression("1.1E+2").getValue(double.class);//110.0int hex1 = parser.parseExpression("0xa").getValue(Integer.class);//10long hex2 = parser.parseExpression("0xaL").getValue(long.class);//10boolean true1 = parser.parseExpression("true").getValue(boolean.class);//trueboolean false1 = parser.parseExpression("false").getValue(boolean.class);//falseObject 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"));// 通过属性:yqStandardEvaluationContext 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);//通过属性:yq1900String dogName1900 =  parser.parseExpression("name + 1900").getValue(dog, String.class);//通过方法:yq1900String getDogName = (String)parser.parseExpression("getName() + 1900").getValue(dog);//通过属性:ylString friendName = (String) parser.parseExpression("friend.name").getValue(dog);//通过属性方法混合:ylString 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);//yl123String 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:yqString dogName22 =   parser.parseExpression("#dog.getName()").getValue(context, String.class);System.out.println("dogName22:"+dogName22);//dogName22:yqString 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 oldString 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.0System.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.DateClass dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);//class java.lang.StringClass stringClass = parser.parseExpression("T(String)").getValue(Class.class);//class com.example.AnimalClass stringClass2 = parser.parseExpression("T(com.example.Animal)").getValue(Class.class);//CEILING = 2   FLOOR=3   trueboolean 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方法   cdeString 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));//   HELLOString 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, 年龄: 25System.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}
}

运算符

在这里插入图片描述


  1. 假设有一个用户类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);//trueboolean isAdult = parser.parseExpression("age >= 18").getValue(context, Boolean.class);//Hello, adultString 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    trueboolean result1 = parser.parseExpression("5 > null").getValue( Boolean.class);// 比较一个负数和null   trueboolean result2 = parser.parseExpression("-3 > null").getValue(Boolean.class);// 尝试比较一个值小于null(根据规则应该总是为假)  falseboolean result3 = parser.parseExpression("2 < null").getValue( Boolean.class);boolean result4 = parser.parseExpression("null < null").getValue( Boolean.class);//falseboolean result5 = parser.parseExpression("null > null").getValue( Boolean.class);//falseboolean result7 = parser.parseExpression("null != null").getValue( Boolean.class);//falseboolean 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());// 结果: falseboolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);// 调用方法并根据方法返回值判断  falseString expression1 = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";boolean trueValue1 = parser.parseExpression(expression1).getValue(context, Boolean.class);// -- OR --   trueboolean trueValue2 = parser.parseExpression("true or false").getValue(Boolean.class);// 调用方法并根据方法返回值判断  trueString expression3 = "isMember('Nikola Tesla') or isMember('Albert Einstein')";boolean trueValue3 = parser.parseExpression(expression3).getValue(context, Boolean.class);// -- NOT --  取反  falseboolean falseValue4 = parser.parseExpression("!true").getValue(Boolean.class);// 调用方法并根据方法返回值判断  falseString expression5 = "!isMember('Nikola Tesla') or isMember('Albert Einstein')";boolean trueValue5 = parser.parseExpression(expression5).getValue(context, Boolean.class);System.out.println(trueValue5);// -- AND and NOT --   trueString 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);// 使用安全导航运算符获取城市名称,此时不会抛出空指针异常,而是返回nullString 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 );//2Integer invention1 = parser.parseExpression("prices[1]").getValue(context, dog, Integer.class);Integer invention2 = parser.parseExpression("getPrices[1]").getValue(context, dog, Integer.class);//yqString invention3 = parser.parseExpression("ls[1]").getValue(context, dog, String.class);String invention4 = parser.parseExpression("getLs[1]").getValue(context, dog, String.class);//yqString 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@NoArgsConstructorpublic 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 2024expressionStr = "这是一个公司信息详情信息页,公司名称:#{[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方法并返回结果。*/// 设置rootObjectStandardEvaluationContext 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名称前加上&符号:

在这里插入图片描述


在这里插入图片描述

这块博主还没深入研究,乏了,等后续用到再看吧

  1. 引入相关依赖(以 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>
  1. 创建普通 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> {@Overridepublic UserService getObject() throws Exception {UserService userService = new UserService();userService.setServiceName("Custom User Service from Factory");return userService;}@Overridepublic Class<?> getObjectType() {return UserService.class;}@Overridepublic boolean isSingleton() {return true;}
}

在上述代码中:
UserService 是一个普通的 Spring Bean,提供了一些简单的业务方法(这里只是简单打印服务名称用于示例)。
UserServiceFactoryBean 是一个实现了 FactoryBean 接口的工厂 Bean,它负责创建 UserService 的实例,并且可以在创建过程中对实例进行一些自定义的初始化操作,比如修改服务名称等。

  1. 自定义 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 实例返回。

  1. 使用 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 实例进行操作。

  1. 使用 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 {@Beanpublic UserService userService() {return new UserService();}@Beanpublic 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());//加减乘除  20System.out.println((parser.parseExpression("8%3").getValue()));//求余  2System.out.println((parser.parseExpression("2.0e3").getValue()));//指数  2000.0System.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.0Double 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.0double 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,可以定义全局的parserExpressionParser 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 DoeSystem.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中的第七个InventionsString invention2 = parser.parseExpression("Members[0].Inventions[6]").getValue(context, ieee, String.class);//    IEEE第一个成员的第七个发明是: 发明7System.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@NoArgsConstructorpublic static class Account {private String name;}@Data@AllArgsConstructorpublic static class Info {private String url ;}@Data@AllArgsConstructorpublic 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);//4System.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));//2Map<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?.infosMap<String, Object> objectMap =(Map) parser.parseExpression("{acName:#account.name,userUrls:#user.infos?.![#this]}").getValue(context);//{acName=Deniro, userUrls=null}System.out.println(objectMap);}}

在这里插入图片描述

相关文章:

Spring SpEL表达式由浅入深

标题 前言概述功能使用字面值对象属性和方法变量引用#this 和 #root变量获取类的类型调用对象(类)的方法调用类构造器类型转换运算符赋值运算符条件(关系)表达式三元表达式Elvis 操作符逻辑运算instanceof 和 正则表达式的匹配操作符 安全导航操作员数组集合(Array 、List、Map…...

WebRTC的线程切换

1. WebRTC的线程切换有哪些方法&#xff1a; Post方法PostTask方法Send方法Invoke方法 其中&#xff0c;Post和PostTask方法是【异步】的&#xff0c;即发送线程发送后无需等待接收线程完成处理&#xff1b; Send和Invode方法是【同步】的&#xff08;发送线程会一直等待接收…...

【three.js】搭建环境

一、安装Node.js和npm 下载与安装&#xff1a; 访问Node.js官方网站&#xff08;nodejs.org&#xff09;&#xff0c;根据你的操作系统下载并安装最新稳定版&#xff08;LTS版本&#xff09;的Node.js。安装过程中&#xff0c;npm&#xff08;Node包管理器&#xff09;会随No…...

【MySQL 保姆级教学】用户管理和数据库权限(16)

数据库账户管理是指对数据库用户进行创建、修改和删除等操作&#xff0c;以控制用户对数据库的访问权限。通过账户管理&#xff0c;可以设置用户名、密码、主机地址等信息&#xff0c;确保数据库的安全性和可控性。例如&#xff0c;使用 CREATE USER 创建用户&#xff0c;ALTER…...

信息科技伦理与道德1:绪论

1 问题描述 1.1 信息科技的进步给人类生活带来的是什么呢&#xff1f; 功能&#xff1f;智能&#xff1f;陪伴&#xff1f;乐趣&#xff1f;幸福&#xff1f; 基于GPT-3的对话Demo DeepFake 深伪技术&#xff1a;通过神经网络技术进行大样本学习&#xff0c;将个人的声音、面…...

HTTP2/3强势来袭

目录 摘要HTTP1/1.1概述HTTP/1.0 vs HTTP/1.1HTTP/1.0中的问题HTTP/1.1的管道化机制为什么HTTP/1.0导致“卡住”什么是队头阻塞 HTTP2兼容 HTTP/1.1头部压缩静态表编码动态表编码伪标头字段 二进制帧并发传输Stream ID的存储位置如何理解Steam&#xff0c;Message&#xff0c;F…...

2025考研江南大学复试科目控制综合(初试807自动控制原理)

​ 2025年全国硕士研究生招生考试江南大学考点 一年年的考研如期而至&#xff0c;我也变成了研二了&#xff0c;作为2次考研经历的学长&#xff0c;总是情不自禁地回想起自己的考研经历&#xff0c;我也会经常从那段经历中汲取力量。我能理解大多数考生考完后的的迷茫无助&…...

Java SpringBoot使用Apache POI导入导出Excel文件

点击下载《Java SpringBoot使用Apache POI导入导出Excel文件(源代码)》 1. Apache POI 简介 Apache POI 是一个强大的 Java 库&#xff0c;用于处理 Microsoft Office 文档&#xff0c;包括 Excel 文件&#xff08;.xls 和 .xlsx&#xff09;。在 Java Spring Boot 项目中&am…...

Web安全扫盲

1、建立网络思维模型的必要 1 . 我们只有知道了通信原理&#xff0c; 才能够清楚的知道数据的交换过程。 2 . 我们只有知道了网络架构&#xff0c; 才能够清楚的、准确的寻找漏洞。 2、局域网的简单通信 局域网的简单通信&#xff08;数据链路层&#xff09; 一般局域网都通…...

8. C++ 面向对象之特性一(封装)

面向对象主要包括三大类&#xff1a;封装&#xff0c;继承&#xff0c;多态 1.类和对象 c认为&#xff0c;万物皆为对象&#xff0c;对象上有其属性和行为 人可以作为对象&#xff0c;属性有姓名、年龄、身高、体重...&#xff0c;行为有走、跑、跳、吃饭、唱歌... 车也可以作…...

软件工程期末复习(一)

题目复习 单选题 软件产品的核心特性是什么&#xff1f; A. 物质性 B. 逻辑性 C. 可复制性 D. 消耗性 正确答案&#xff1a;B 单选题 在软件开发过程中&#xff0c;哪个环节最接近于传统制造业中的“生产”过程&#xff1f; A. 需求分析 B. 编码 C. 测试 D. 研制&#xff08…...

什么是Kafka的重平衡机制?

Kafka 的重平衛机制是指在消费者组中新增或删除消费者时&#xff0c;Kafka 集群会重新分配主题分区给各个消费者&#xff0c;以保证每个消费者消费的分区数量尽可能均衡。 重平衡机制的目的是实现消费者的负载均衡和高可用性&#xff0c;以确保每个消费者都能够按照预期的方式…...

基于Python读取ZIP和TAR格式压缩包教程

在数据处理和文件管理中&#xff0c;压缩包&#xff08;如ZIP、TAR等格式&#xff09;的使用非常普遍。Python提供了多种库来读取和处理这些压缩包。本文将介绍如何使用Python的内置库和第三方库来读取ZIP和TAR格式的压缩包。 1、读取ZIP文件 Python的zipfile模块提供了处理Z…...

麒麟服务器安装kafka--亲测

我这安装的是单机版本的&#xff1a; 下载地址&#xff1a;Index of /kafka/3.9.0 我下载的是&#xff1a;https://dlcdn.apache.org/zookeeper/zookeeper-3.9.3/apache-zookeeper-3.9.3-bin.tar.gz https://dlcdn.apache.org/kafka/3.9.0/kafka_2.12-3.9.0.tgz 一、下载并上…...

5G NTN(七) 高层(1)

说明&#xff1a;本专题主要基于3GPP协议38.821 目录 1. Idle态移动性增强 1.1 TA问题 1.1.1 TA的大小 1.1.2 针对NTN LEO的移动TA&#xff0c;场景C2和D2 1.1.3 针对NTN LEO的固定TA&#xff0c;场景C2和D2 1.1.3.1 方法1&#xff1a;当UE位置信息无法获取的时候 1.1.…...

git:指令集

以下是对这些 Git 新特性和命令的更详细解读和实际用例分析&#xff0c;帮助更好地理解它们的作用及适用场景&#xff1a; 1. git switch 和 git restore 背景&#xff1a; 传统上&#xff0c;git checkout 是一个多功能命令&#xff0c;用于切换分支、检出文件、创建分支等&…...

【Vue学习】Vue 组件实例的生命周期(四个阶段,八个钩子)

一、为什么要理解生命周期&#xff1f; 理解生命周期就像是知道了一部电影的剧情走向&#xff0c;能让你在适当的时机做出反应。Vue 生命周期的钩子让你可以在不同的阶段插入你的逻辑&#xff0c;像是提前准备、后期清理或者在数据更新时做点事情。这种“精确控制”的能力会让你…...

第27周:文献阅读及机器学习

目录 摘要 Abstract 一、文献阅读 发现问题 研究方法 CNN-LSTM DT SVR 创新点 案例分析 数据准备 模型性能 预测模型的实现 仿真实验及分析 二、LSTM 1、基本结构 2、具体步骤 3、举例说明 4、原理理解 总结 摘要 本周阅读文献《Short-term water qua…...

Tailwind CSS 实战:动画效果设计与实现

在现代网页设计中,动画效果就像是一位优秀的舞者,通过流畅的动作为用户带来愉悦的视觉体验。记得在一个产品展示网站项目中,我们通过添加精心设计的动画效果,让用户的平均停留时间提升了 35%。今天,我想和大家分享如何使用 Tailwind CSS 打造优雅的动画效果。 设计理念 设计动…...

在K8S中,Pod请求另一个Pod偶尔出现超时或延迟,如何排查?

在Kubernetes中&#xff0c;当Pod请求另一个Pod时偶尔出现超时或延迟&#xff0c;可能是由于多种原因造成的。以下是一些建立的排查步骤&#xff1a; 1. 检查网络配置和插件&#xff1a; 确认你的kubernetes集群使用了合适的网络插件&#xff08;如Calico、Flannel等&#xf…...

C# 设计模式(结构型模式):外观模式

C# 设计模式&#xff08;结构型模式&#xff09;&#xff1a;外观模式 (Facade Pattern) 在复杂系统中&#xff0c;往往会涉及到多个子系统、模块和类。这些子系统的接口和功能可能会让使用者感到困惑和复杂。在这种情况下&#xff0c;我们可以使用外观模式&#xff08;Facade…...

LLM - 使用 LLaMA-Factory 部署大模型 HTTP 多模态服务 教程 (4)

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/144881432 大模型的 HTTP 服务,通过网络接口,提供 AI 模型功能的服务,允许通过发送 HTTP 请求,交互大模型,通常基于云计算架构,无需在本地部署复杂的模型和硬件,…...

MCGS学习记录

软件包 用户窗口 主窗口 元件&#xff1a;工具箱->输入框上面 数据对象 在工作台的实时数据库可以新增数据对象 理解为中间变量&#xff0c;控件改变其值&#xff0c;控件监测其值做出变化 基本属性 设定变量名和初始值 指针化&#xff1f; 变化时自动保存初始值&#x…...

Swift Protocols(协议)、Extensions(扩展)、Error Handling(错误处理)、Generics(泛型)

最近在学习 Swift&#xff0c;总结相关知识 1. Protocols&#xff08;协议&#xff09; 1.1 协议的定义和实现 协议&#xff08;protocol&#xff09; 是一种定义方法和属性的蓝图&#xff0c;任何类、结构体或枚举都可以遵循协议。遵循协议后&#xff0c;需要实现协议中定义…...

axios和fetch的实现原理以及区别,与XMLHttpRequest的关系,并结合react封装统一请求示例

Axios 和 Fetch 对比及统一请求封装 1. Axios 基础用法 1.1 安装和引入 // 安装 npm install axios// 引入 import axios from axios;1.2 基本请求方法 // GET 请求 axios.get(/api/users).then(response > console.log(response.data)).catch(error > console.error…...

2024年阅读书单

《高效能人士的7个习惯》 史蒂芬.柯维 精进 《高效能人士的7个习惯》在读了一遍之后&#xff0c;记住非常深刻的就是积极主动和以终为始。 《软件架构》 Simon Brown 架构、技术 《软件架构》这本书比较抽象&#xff0c;若是工作时间比较少的人来读&#xff0c;可能觉得作者没写…...

Eclipse 内容辅助

Eclipse的内容辅助&#xff08;Content Assist&#xff09;功能是一项核心特性&#xff0c;它通过提供代码提示和自动完成建议来提高开发效率。这个功能能够在用户编写代码时自动显示可能的代码补全选项&#xff0c;如变量名、方法名、类名和关键字等。它还能根据用户的输入和上…...

【数学建模笔记】评价模型-基于熵权法的TOPSIS模型

视频课地址&#xff1a;https://www.bilibili.com/video/BV1eRyVYUEhg 本系列文章和课程一样&#xff0c;只使用Python实现&#xff0c;好久没玩数学建模了 国赛中不能再用TOPSIS&#xff0c;可以做辅助算法。 1. 算法原理 熵权TOPSIS方法是一种结合熵权法和TOPSIS的决策分析…...

小程序发版后,强制更新为最新版本

为什么要强制更新为最新版本&#xff1f; 在小程序的开发和运营过程中&#xff0c;强制用户更新到最新版本是一项重要的策略&#xff0c;能够有效提升用户体验并保障系统的稳定性与安全性。以下是一些主要原因&#xff1a; 1. 功能兼容 新功能或服务通常需要最新版本的支持&…...

数据分析思维(七):分析方法——群组分析方法

数据分析并非只是简单的数据分析工具三板斧——Excel、SQL、Python&#xff0c;更重要的是数据分析思维。没有数据分析思维和业务知识&#xff0c;就算拿到一堆数据&#xff0c;也不知道如何下手。 推荐书本《数据分析思维——分析方法和业务知识》&#xff0c;本文内容就是提取…...

SAP 01-初识AMDP(ABAP-Managed Database Procedure)

1. 什么是AMDP(ABAP-Managed Database Procedure) 1.&#xff09;AMDP - ABAP管理数据库程序&#xff0c;是一种程序&#xff0c;我们可以使用SQLSCRIPT在AMDP内部编写代码&#xff0c;SQLSCRIPT是一种与SQL脚本相同的数据库语言&#xff0c;这种语言易于理解和编码。 将AM…...

.net core 线程锁,互斥锁,自旋锁,混合锁

线程锁、互斥锁、自旋锁和混合锁是多线程编程中的重要概念&#xff0c;它们用于控制对共享资源的访问&#xff0c;避免数据竞争和不一致性。每种锁有其特定的适用场景和特点。我们来逐一解释它们&#xff0c;并进行比较。 1. 线程锁&#xff08;Thread Lock&#xff09; 线程…...

大数据系列之:深入理解学习使用腾讯COS和COS Ranger权限体系解决方案,从hdfs同步数据到cos

大数据系列之&#xff1a;深入理解学习使用腾讯COS和COS Ranger权限体系解决方案&#xff0c;从hdfs同步数据到cos 对象存储COS对象存储基本概念COS Ranger权限体系解决方案部署组件COS Ranger Plugin部署COS-Ranger-Service部署COS Ranger Client部署 COSN 从hdfs同步数据到co…...

Uniapp Android 本地离线打包(详细流程)

一、简介 App 离线 SDK 暂时不支持 Kotlin&#xff0c;未来不清楚。 uniapp 提供了 云打包 与 本地打包 两种方案&#xff0c;云打包 需要排队且还有次数限制&#xff0c;本地打包 则就没有这些限制&#xff0c;而且会 本地打包 对开发 原生插件 有很大的帮助。 细节&#x…...

单片机常用外设开发流程(1)(IMX6ULL为例)

1.通过GPIO引脚控制led灯和key按钮 &#xff08;1&#xff09;设置多路复用的引脚&#xff08;SW_MUX_CTL&#xff09;也可以说是选择让引脚以哪种工作模式工作 &#xff08;2&#xff09;设置电器属性&#xff08;SW_PAD_CTL&#xff09;上拉、等等... (3)设置GPIO的方向&am…...

机器学习 学习知识点

机器学习 学习知识点 什么是消融实验&#xff08;Ablation experiment&#xff09;&#xff1f;num_step与batch_size的区别python glob.glob()函数认识python的条件判断之is not、is not None、is Nonetqdm介绍及常用方法softmax 激活函数。type_as(tesnor)Python OpenCV cv2.…...

深入了解PINN:物理信息神经网络(Physics-Informed Neural Networks)

1. 什么是PINN&#xff08;物理信息神经网络&#xff09;&#xff1f; 物理信息神经网络&#xff08;PINN&#xff0c;Physics-Informed Neural Networks&#xff09;是一类通过结合神经网络和物理方程的深度学习方法。其主要特点是将物理系统的约束条件&#xff08;如偏微分方…...

人工智能知识分享第八天-机器学习_泰坦尼克生存预估线性回归和决策树回归对比案例

泰坦尼克生存预估案例 import pandas as pd from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import classification_report import matplotlib.pyplot as plt from sklearn.tree import plot_t…...

封装/前线修饰符/Idea项目结构/package/impore

目录 1. 封装的情景引入 2. 封装的体现 3. 权限修饰符 4. Idea 项目结构 5. package 关键字 6. import 关键字 7. 练习 程序设计&#xff1a;高内聚&#xff0c;低耦合&#xff1b; 高内聚&#xff1a;将类的内部操作“隐藏”起来&#xff0c;不需要外界干涉&#xff1b…...

python中的装饰器

装饰器&#xff08;Decorator&#xff09;是 Python 中的一种语法糖&#xff0c;它允许你修改或增强函数或类的行为。下面详细解释&#xff1a; 基本概念 装饰器本质上是一个函数&#xff0c;它接受一个函数作为参数&#xff0c;并返回一个新的函数&#xff1a; def my_decor…...

HTMLElement、customElements及元素拓展

文章目录 HTMLElement 与 customElementscustomElements.define() 方法说明HTML 元素方法拓展 HTMLElement 与 customElements HTMLElement 概述 HTMLElement是一个接口&#xff0c;它表示所有HTML元素。几乎所有的HTML标签&#xff08;如<div>、<p>、<a>等…...

字玩FontPlayer开发笔记3 性能优化 大量canvas渲染卡顿问题

字玩FontPlayer开发笔记3 性能优化 大量canvas渲染卡顿问题 字玩FontPlayer是笔者开源的一款字体设计工具&#xff0c;使用Vue3 ElementUI开发&#xff0c;源代码&#xff1a; github: https://github.com/HiToysMaker/fontplayer gitee: https://gitee.com/toysmaker/fontpl…...

PyTorch 中 coalesce() 函数详解与应用示例

PyTorch 中 coalesce() 函数详解与应用示例 coalesce&#xff1a; 美 [ˌkoʊəˈlɛs] 合并&#xff1b;凝聚&#xff1b;联结&#xff0c;注意发音 引言 在 PyTorch 中&#xff0c;稀疏张量&#xff08;Sparse Tensor&#xff09;是一种高效存储和操作稀疏数据的方式。稀疏…...

计算机网络(第8版)第3章课后习题--透明传输

【3-11】 试分别讨论以下各种情况在什么条件下是透明传输&#xff0c;在什么条件下不是透明传 输。(提示&#xff1a;请弄清什么是“透明传输”,然后考虑能否满足其条件。) (1)普通的电话通信。 (2)互联网提供的电子邮件服务。 解 答 &#xff1a; 透明传输是指在数据传输…...

JavaScript 日期格式

在 JavaScript 中,日期格式可以通过 Date 对象进行操作和格式化。下面是一些常见的 JavaScript 日期格式及其示例: 1. ISO 8601 格式 ISO 8601 是一种标准的日期和时间表示方法,格式为 YYYY-MM-DDTHH:mm:ss.sssZ,例如: let date = new Date(); console.log(date.toISOS…...

云打印之拼多多打印组件交互协议

拼多多打印组件交互协议相关介绍如下&#xff1a; 1、打印组件下载地址 http://meta.pinduoduo.com/api/one/app/v1/lateststable?appIdcom.xunmeng.pddprint&platformwindows&subTypemain 2、socket连接端口 如果是http的话&#xff0c;端口是5000 socket new …...

Oracle数据库如何找到 Top Hard Parsing SQL 语句?

有一个数据库应用程序存在过多的解析问题&#xff0c;因此需要找到产生大量硬解析的主要语句。 什么是硬解析 Oracle数据库中的硬解析&#xff08;Hard Parse&#xff09;是指在执行SQL语句时&#xff0c;数据库需要重新解析该SQL语句&#xff0c;并创建新的执行计划的过程。这…...

浅谈棋牌游戏开发流程二:后端技术选型与基础环境搭建

一、前言&#xff1a;客户端只是台前&#xff0c;后端才是幕后“指挥中心” 在上一篇“客户端技术”中&#xff0c;我们聊到玩家看到的一切动作、动画、界面逻辑&#xff0c;都靠客户端去渲染和交互。但若没有后端的支撑&#xff0c;玩家点了“出牌”可能就像一拳打在空气里—…...

使用qiankun搭建微前端应用及踩坑

线上演示地址&#xff1a;React App 源码地址&#xff1a;https://github.com/Jiang-K-J/micro-app?tabreadme-ov-file &#xff08;帮忙点个小星星&#xff09; 主应用&#xff1a;react 18 子应用&#xff1a;vite vue3 子应用&#xff1a;react 18 安装 主应用 $ y…...

Windows 环境配置 HTTPS 服务实战

一、 环境准备 win10以上操作系统安装 Certbot申请阿里云\腾讯云域名安装 nginx 1.3以上版本 二、Certbot 安装及 SSL 证书生成 Certbot 是一个免费、开源工具&#xff0c;用于自动化在Web服务器上获取和更新SSL/TLS证书。它可以通过Let’s Encrypt服务获取免费的SSL/TLS证书…...