SpEL表达式
知识点
Spel概述
Spring 表达式,即 Spring Expression Language,简称 SpEL。
那么是什么SpEL表达式呢?
- SpEL (Spring Expression Language) 是一种在Spring框架中用于处理表达式的语言。SpEL中的表达式可以支持调用bean的方法、进行数学运算、进行逻辑判断和条件判断等功能,利用SpEL表达式的这些特点可以使得Spring应用在配置和处理数据时更加灵活和方便,可以让这个字符串有逻辑的执行或者调用方法。
SpEL表达式的语法是什么样的?
- 语法类似于 EL表达式(如果有不懂EL表达式和SpEL表达式的区别可以看下面的"补充内容1")。
SpEL表达式主要用于哪里?
-
在注解**@Value**中使用(这个其实我个人觉得和第三点类似,@Value中能写SpEL表达式,是因为@Value的解析程序用了Expression对象来解析了SpEL表达式,SpringBoot会自动调用这个内部解析程序,所以可以写。就像SpringSecurity中方法上的注解@PreAuthorize("@ss.hasPermi('monitor:server:list')")一样,@PreAuthorize注解可以使用SpEL表达式的写法,也是因为内置的解析程序底层用了Expression对象来解析SpEL表达式。所以我认为,@Value等所有内置的、支持使用SpEL表达式的注解,都是可以属于第三种情况的。)
注意:@Value中支持的不止SpEL表达式,但是它也支持你使用其他的非SpEL的写法。就是,在@Value中,默认情况下如果是#{}包裹起来的就是当作SpEL表达式去处理,其他的不会被当作SpEL表达式来处理。但是如果你特别配置了,那么模板可能就不是#{}了,具体怎么配置看下面笔记的《在Bean定义中SpEL的问题》
-
在XML配置中使用(SpringBoot的配置XML配置中也可以写SpEL表达式,这个我觉得也是因为SpringBoot底层解析XML的程序中有考虑到SpEL表达式的写法,并且使用了Expression对象来解析SpEL表达式,所以XML中才可以写的SpEL表达式,你自己平白无故写的XML中写SpEL表达式不会有任何左右,只会被当做是普通字符串)
-
在代码 中创建Expression对象,然后自己利用Expression对象来执行SpEL表达式字符串。(比如,我们可以自定义注解,然后注解中写SpEL表达式格式的字符串。然后我们自己用AOP解析去截取所有这个注解的方法,并且拿到注解里面的SpEL表达式,再解析执行这个SpEL表达式,然后去增强你要增强的方法。)
SpEL表达式的执行时机是什么时候?
-
@Value和Expression对象解析SpEL表达式都是在解析程序执行的时候去执行SpEL表达式的。比如@Value这个注解是在项目启动的时候进行解析的,所以会在项目启动的是执行解析程序。所以@Value中的SpEL表达式也是在项目启动的时候被解析执行的。
证明:(下面这个例子,让项目启动的时候,通过@Value去调用静态方法,给aa赋值)
启动时:
登录后:
现象:启动的时候,看到这个静态方法被调用了,所以说明启动的时候执行SpEL表达式的解析的,然后SpEL去调用方法,所以打印了"绑定成功!"。那么数据是否注入成功了呢?我们看看登录的时候,是否有去执行getLoginUser()方法就行了。我们看到打印"执行了::staticMethod绑定成功",所以证明数据已经注入到了aa变量中了,即@Value是成功绑定值给变量的。
-
对于XML等配置文件中的SpEL表达式什么时候解析,我估计也是在XML配置文件被解析或者说读取的时候进行的。因为比如,我们写一个不会被用到的XML文件,并且里面写了SpEL表达式,那么这个SpEL表达式是永远不会执行的,以此推断XML等配置文件中的SpEL表达式被解析执行的时机也是一样的,是在XML配置文件被解析的时候执行的。
SpEL原理及解析接口
基本使用
SpEL在求表达式值时一般分为四步,其中第三步可选。
- 首先构造一个解析器
- 其次使用解析器生成表达式对象
- 然后构造评估上下文对象。
- 最后根据评估上下文得到表达式运算后的值
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test1() {
// 1. 创建解析器
ExpressionParser parser = new SpelExpressionParser();
// 2. 生成解析表达式
Expression expression = parser.parseExpression("('Hello' + ' World').concat(#end)");// "('Hello' + ' World').concat(#end)",这个表达式包含了字符串拼接操作和concat函数调用,其中#end表示一个变量,变量的值是放在评估上下文中的。concat函数就是用于拼接的。(SpEL表达式中的#XX就是取环境中的变量哈,这种没有定义模板的,整个表达式都是会被当作SpEL表达式解析的,其中#end会被替换为环境中的具体值的)
// 3. 构造评估上下文对象,并设置需要的环境值
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("end", "!");
// 4. 求值
System.out.println(expression.getValue(context));
}
}
输出:
java
Hello World!
接下来让我们分析下代码:
1)创建解析器:ExpressionParser接口表示解析器,SpelExpressionParser是SpEL表达式的解析器;
2)解析表达式:使用ExpressionParser的parseExpression方法来生成Expression表达式对象;
3)构造评估上下文:放一些变量、方法等环境数据的对象;
4)求值:通过Expression接口的getValue方法,可以做到让表达式根据评估上下文环境来获得对应的结果。
SpEL原理
介绍原理前让我们学习下几个概念:
一、表达式: 表达式是表达式语言的核心,所以表达式语言都是围绕表达式进行的,从我们角度来看是"干什么?怎么干?",强调干的过程;
二、解析器: 用于将字符串表达式解析为表达式对象工具,从我们角度来看就是"谁来干",解析器就是来干的人;
三、评估上下文: 表达式对象执行的环境,该环境可能定义变量、定义自定义函数、提供类型转换等等,从我们角度看是"在哪干";
四、根对象及活动上下文对象: 根对象是默认的活动上下文对象,根对象默认就是#this,但是活动上下文不一定指定就是根对象,因为活动上下文会变,一般在List和map中活动上下文会变,活动上下文对象表示了当前表达式操作的对象,从我们角度看是"对谁干"。
理解了这些概念后,让我们看下SpEL如何工作的呢,如图所示:
举一个例子:1+2
工作原理:
java
1.首先定义表达式:比如表达式是"1+2";
2.定义解析器ExpressionParser实现,SpEL表达式对应的解析器是SpelExpressionParser;
2.1.SpelExpressionParser解析器内部使用Tokenizer类进行词法分析,即把字符串流分析为记号流。记号在SpEL中使用Token类来表示;
2.2.有了记号流后,解析器便可根据记号流生成内部抽象语法树;在SpEL中语法树节点是由SpelNode接口定义的。这些SpelNodel节点可以形成抽象语法树。比如:如OpPlus表示加操作节点、IntLiteral表示int型字面量节点;
2.3.SpEL表达式解析器生成的Expression对象,就是一棵抽象语法树,Expression对象提供getValue方法,可以用于获取表达式的值;
3.定义评估上下文对象(可选),EvaluationContext接口表示评估上下文对象,可以在评估上下文中设置根对象、自定义变量、自定义函数、类型转换器等。SpEL提供的评估上下文实现是StandardEvaluationContext和SimpleEvaluationContext,我们一般用StandardEvaluationContext;
4.使用表达式对象根据评估上下文对象(可选)求值(调用表达式对象的getValue方法)获得结果。
SpelNode是SpEL中的节点类型,在SpelExpressionParser中用于构建和表示解析后的表达式的抽象语法树(AST)。SpelExpressionParser的解析过程会将SpEL表达式拆分成不同类型的节点,每个节点代表表达式中的一个操作或函数调用。
SpelNode是AST中的节点,可以是操作符节点、函数调用节点、属性节点等,它们之间通过父子关系组织成一个树形结构。在表达式求值过程中,SpelExpressionParser会递归地遍历这棵树,并根据每个节点的类型执行相应的求值操作。
简单地总结SpEL表达式的原理:
SpelExpressionParser是一个基于Spring表达式语言(SpEL)的解析器。SpEL是一种强大的表达式语言,它允许在运行时动态地访问和操作对象的属性、方法和其他数据。
SpelExpressionParser的原理是将SpEL表达式解析成一个抽象语法树(AST),然后根据这个AST对对象进行求值。在解析SpEL表达式时,SpelExpressionParser会根据表达式中的操作符和函数调用将其拆分成不同的节点,然后递归地构建AST,最终会形成一个树状的结构。
当SpEL表达式被解析成AST后,SpelExpressionParser的getValue方法会根据AST对对象进行求值,并返回表达式的结果。在表达式求值过程中,SpelExpressionParser会根据节点类型进行相应的处理,比如访问对象的属性、调用对象的方法或执行算术运算,这些东西都是在评估上下文中的。
总的来说,SpelExpressionParser的原理是将SpEL表达式解析成一个抽象语法树,然后根据这个AST对对象进行求值,从而实现动态访问对象的属性、方法或者操作对象的属性、方法。
主要接口
接下来让我们看下SpEL的主要接口吧。
ExpressionParser接口
表示解析器,SpEL对应的解析器实现是org.springframework.expression.spel.standard包中的SpelExpressionParser类,SpelExpressionParser对象可以使用parseExpression方法将字符串表达式转换为Expression对象。
SpelExpressionParser接口的主要作用是:把字符串就是把字符串转为Expression对象。或者指定模板,只把指定范围内的东西当作表达式进行SpEL解析了。
parseExpression全部API如下:
java
public interface ExpressionParser {
// 不传解析上下文对象
Expression parseExpression(String expressionString) throws ParseException;
// 传一个自定义的解析上下文对象。
Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
}
其中第二个方法的第二个参数ParserContext接口,这个接口就是用来定义模板的。
例子:
没有定义模板的情况下:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testParserContext() {
ExpressionParser parser = new SpelExpressionParser();
String s = "1+2-3*4/2";
// 加减乘除
String result1 = parser.parseExpression(s).getValue(String.class);
System.out.println("result1=" + result1);
}
}
结果:
java
result1=-3
字符串s中的内容都会被当作SpEL表达式解析。
如果我们想只解析3*4这个表达式,那么我们可以定义模板,然后把我们要当作SpEL表达式解析的东西包裹起来就行了。
比如:使用默认的模板。
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testParserContext() {
ExpressionParser parser = new SpelExpressionParser();
//定义模板。默认是以#{开头,以#结尾
ParserContext parserContext = new TemplateParserContext();
String s="1+2-#{3*4}/2";
// 加减乘除
String result1 = parser.parseExpression(s,parserContext).getValue(String.class);
System.out.println("result1=" + result1);
}
}
结果:
java
result1=1+2-12/2
如果,如果#{}是普通文本,是不需要被解析的文本,那么你要怎么做呢?答:你可以自定义模板。
比如:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testParserContext() {
ExpressionParser parser = new SpelExpressionParser();
// ParserContext就是指解析上下文对象。
ParserContext parserContext = new ParserContext() {
// 返回一个布尔值,表示当前解析上下文是否为模板。
@Override
public boolean isTemplate() {
return true;
}
// 当前解析上下文中的表达式前缀。
@Override
public String getExpressionPrefix() {
return "#1{";
}
// 当前解析上下文中的表达式后缀。
@Override
public String getExpressionSuffix() {
return "}";
}
};
String s="1+#{不需要被解析的内容}2-#1{3*4}/2";
// 加减乘除
String result1 = parser.parseExpression(s,parserContext).getValue(String.class);
System.out.println("result1=" + result1);
}
}
结果:
java
result1=1+#{不需要被解析的内容}2-12/2
与上面效果一样的写法,但是下面这个写法更加简单:
javapackage com.example.springbootdemo; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.expression.ParserContext; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; @SpringBootTest class SpringbootDemoApplicationTests { @Test public void test11() { //创建解析器 SpelExpressionParser parser = new SpelExpressionParser(); //创建评估上下文 ParserContext context = new TemplateParserContext("#1{", "}"); String s="1+#{不需要被解析的内容}2-#1{3*4}/2"; // 加减乘除 String result1 = parser.parseExpression(s,context).getValue(String.class); System.out.println("result1=" + result1); } }
结果:
javaresult1=1+#{不需要被解析的内容}2-12/2
看看源码自己体会原理,这个很简单,不解释了:
通过上面的这个源码截图,你也可以体会到前面的例子,即下面这个代码中为什么默认的模版是以#{开头,以#结尾的。
javapackage com.example.springbootdemo; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.expression.ExpressionParser; import org.springframework.expression.ParserContext; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; @SpringBootTest class SpringbootDemoApplicationTests { @Test public void testParserContext() { ExpressionParser parser = new SpelExpressionParser(); //定义模板。默认是以#{开头,以#结尾 ParserContext parserContext = new TemplateParserContext(); String s="1+2-#{3*4}/2"; // 加减乘除 String result1 = parser.parseExpression(s,parserContext).getValue(String.class); System.out.println("result1=" + result1); } }
看到只有"#1{"和"}"之间的字符串被解析了,其他的都被当作普通的字符串了。
分析:
- 在这段代码中。我们首先创建了一个SpelExpressionParser实例来实例化 ExpressionParser 接口。
- 然后通过实现ParserContext接口的匿名类来创建了一个自定义的解析上下文对象,并重写了isTemplate、getExpressionPrefix和getExpressionSuffix方法。在这个ParserContext中,isTemplate方法返回true表示定义的这个对象是一个模板对象。getExpressionPrefix方法返回"#1{"表示模板表达式的前缀。 getExpressionSuffix方法返回"}"表示模板表达式的后缀。所以在"#1{"和"}"之间的字符串将会被认为是SpEL表达式要当作SpEL表达式解析的表达式。
- 接着定义了一个SpEL表达式字符串s。
- 然后通过getValue方法来解析这个字符串。
- 最后输出结果。
如果模板中有变量,那么就在模板中使用#XX来取就行了。
比如:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testParserContext() {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("a1",100);// 设置变量
context.setVariable("a2",200);// 设置变量
//定义模板。默认是以#{开头,以#结尾
ParserContext parserContext = new TemplateParserContext();
String s="1+2-#{94+#a1+3*4}/2";
// 加减乘除
String result1 = parser.parseExpression(s,parserContext).getValue(context,String.class);
System.out.println("result1=" + result1);
}
}
结果:
java
result1=1+2-206/2
求值的时候,会先把变量替换为对应的值后,然后再计算这个表达式的值的。
注意哈,如果表达式字符串中没有要替换的,那么评估上下文其实也是可以不用传的,比如上面没有使用#a1的例子,这就是前面说的评估上下文对象是可选的原因。这里说的评估上下文不是解析上下文哈。是这个评估上下文:
getValue()方法就是用来获取表达式值的方法,就是求值的方法。
Expression接口
表示表达式对象,默认实现是org.springframework.expression.spel.standard包中的SpelExpression,提供getValue方法用于获取表达式值,提供setValue方法用于设置变量的值。
Expression的关键API如下:
使用例子:
相当于是赋值,改变表达式对应的评估上下文中变量的值:
下面是全面了解getValue和setValue的例子,同时有助于了解#root
综合的例子,可以更好的理解setValue和getValue方法:(到底是调用哪个重载的getValue和setValue方法,自己看上面的API声明就知道了)
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testVariableExpression() {
ExpressionParser parser = new SpelExpressionParser();
User user = new User("张三", 20);
// 使用#root来访问根对象属性.
String s = "'根对象:'+#root";
Expression expression = parser.parseExpression(s);
System.out.println(expression.getValue());// 使用解析器内部默认的评估上下文对象,默认的评估上下文对象中你没有设置根对象,那么返回就是null。默认的评估上下文可能不能设置根对象哈,反正我不知道怎么设置。
// 输出:根对象:null
System.out.println(expression.getValue(user));// 这样就是给定根对象了,但是并没有设置到解析器内部默认的评估上下文中去。你可以理解为借用这个user作为根对象,然后输出一下就还回去了。
// 输出:根对象:User{name='张三', age=20}
System.out.println(expression.getValue());// 因为上面的expression.getValue(user)没有设置根对象到解析器内部默认的评估上下文中去,所以这里还是返回null,相当于上面只是借用了user这个根对象然后执行getValue了,所以输出看到打印根对象了,用完又还回去了
// 输出:根对象:null
System.out.println(expression.getValue(user,String.class));// 这个写法也是和上面的expression.getValue()一样,只是把返回值类型指定为String.class。即,先借用根对象执行getValue方法,输出后,用完又还回去了
// 输出:根对象:User{name='张三', age=20}
System.out.println(expression.getValue());// 因为还回去了,所以输出根对象为null
// 输出:根对象:null
EvaluationContext context = new StandardEvaluationContext();
System.out.println(expression.getValue(context));// 借用context作为评估上下文对象,然后看看表达式解析的结果。因为这个context没有设置根对象,所以结果会看到null
// 输出:根对象:null
System.out.println(expression.getValue(context,user));// 借用context作为评估上下文对象,又借用user作为评估上下文的根对象,用完context把user还给expression,expression把user和context都还回去。
// 输出:根对象:User{name='张三', age=20}
System.out.println(expression.getValue(context));// 因为还回去了,所以这个context中的根对象还是null
// 输出:根对象:null
EvaluationContext context2 = new StandardEvaluationContext(user);// 只有这样才是把根对象绑定到这个评估上下文中去
System.out.println(expression.getValue(context2));
// 输出:根对象:User{name='张三', age=20}
System.out.println(expression.getValue(context2,new User("李四",30)));// 借用context2作为expression的评估上下文对象,又借用new User("李四",30)作为评估上下文的根对象,用完也是会还回去的。还回去后context2的根对象还是User{name='张三', age=20}。Expression内默认评估上下文的根对象还是null。
// 输出:根对象:User{name='李四', age=30}
System.out.println(expression.getValue(context2));// 因为还回去了,所以这个context2中的根对象还是User{name='张三', age=20}
// 输出:根对象:User{name='张三', age=20}
System.out.println(expression.getValue());// 因为expression用完根对象也是会还回去的,所以这里输出null
// 输出:根对象:null
// 综上所述:只有默认评估上下文对象中的根对象是null,目前我暂时不知道怎么设置默认的评估上下文的根对象不是null。指定评估上下文对象执行getValue方法,expression用完后,会把评估上下文还回去的,只是在getValue执行的时候用一下。expression借用评估上下文对象,同时也可以借用根对象作为评估上下文对象的根对象,用完后,评估上下文对象不会拥有这个根对象的,并且expression也不会拥有这个评估上下文对象的,都是会还回去的,所以expression中默认的评估上下文中根对象还是null,这个指定的评估上下文中的根对象也还是原来的根对象。评估上下文对象设置根对象只能在创建的时候通过参数指定,这样就是绑定了,不然,通过getValue的方式只是借用,暂时应付一下这个getValue方法的返回而已。
EvaluationContext context3 = new StandardEvaluationContext();
context3.setVariable("a1",100);// 设置变量
context3.setVariable("a2",200);// 设置变量
// expression.setValue(context3,user);// 错误,setValue是给表达式的当做变量来赋值的。但是`'根对象:'+#root`可不是变量。所以报错了
Expression expression2 = parser.parseExpression("#root");
expression2.setValue(context3,999);// 没有报错,但是无效,应该是根对象是不能被改变的,在评估上下文被创建的时候就被锁死了。
System.out.println(expression.getValue(context3));
// 输出:根对象:null
Expression expression3 = parser.parseExpression("#a1");
expression3.setValue(context3,user);
System.out.println(expression3.getValue(context3));// 虽然#a1没有对应的set方法,但是这样的setValue方法改变对应的值。并且这个不是借用,而是真正地影响到根对象中的变量的值了。
// 输出:User{name='张三', age=20}
Expression expression4 = parser.parseExpression("#a1.name");
expression4.setValue(context3,"王五");// 前面已经真正改变了#a1的值为一个User了,现在改变这个User中的属性值了,改变属性值这里底层用的是set方法。setValue方法执行的时候会调用的对应的set方法的,所以执行完这个setValue方法,就打印了`setName方法执行了`。
// 输出:setName方法执行了
System.out.println(expression4.getValue(context3));
// 输出:王五
System.out.println(parser.parseExpression("#a1").getValue(context3));// 看到真正改变了context3中的a1的值
// 输出:User{name='王五', age=20}
// 综上:所以setValue方法执行的时候就会调用对应的set方法。当然也不一定,对于#a1,这种的,不好找对应的set方法,也一样可以执行成功(集合、字典也是一样哈,不好找对应的set方法,但是一样可以完成赋值的)。对于变量的某个属性的赋值就是去找set方法的。还有就是setValue会真的改变上下文对象中的变量的值的,不是借用展示一下哈。#root不能被setValue方法赋值,即一个评估上下文中的根对象只能在创建对象的时候绑定,其他时候都不能绑定。
System.out.println(user);// setValue是会影响引用对象的值的。因为相当于是#a1绑定引用,执行改变a1的name,那么就会改变引用的name属性。如果是把a1赋值为其他的对象,那么只是相当于是把"a1=其他的对象的引用",那么这样的话是不会影响原来对象的值的,这个可以理解。
// 输出:User{name='王五', age=20}
}
}
class User {
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("setName方法执行了");
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
输出:
java
根对象:null
根对象:User{name='张三', age=20}
根对象:null
根对象:User{name='张三', age=20}
根对象:null
根对象:null
根对象:User{name='张三', age=20}
根对象:null
根对象:User{name='张三', age=20}
根对象:User{name='李四', age=30}
根对象:User{name='张三', age=20}
根对象:null
根对象:null
User{name='张三', age=20}
setName方法执行了
王五
User{name='王五', age=20}
User{name='王五', age=20}
EvaluationContext接口
表示评估上下文,默认实现是org.springframework.expression.spel.support包中的StandardEvaluationContext类,使用setVariable方法来注册自定义变量,使用registerFunction来注册自定义函数等等。
EvaluationContext接口的全部API如下:
java
public interface EvaluationContext {
// 返回默认的根上下文对象,可以在评估表达式时被覆
TypedValue getRootObject();
// 返回访问器列表用于属性的读写访问
List<PropertyAccessor> getPropertyAccessors();
// 返回解析器列表用于定位构造函数。
List<ConstructorResolver> getConstructorResolvers();
// 返回方法解析器以查找方法
List<MethodResolver> getMethodResolvers();
// 返回 bean解析器以通过名称查找bean
@Nullable
BeanResolver getBeanResolver();
// 返回类型定位器用于查找类型,支持简单类型名称和全程
TypeLocator getTypeLocator();
// 返回类型转换器用于类型转换
TypeConverter getTypeConverter();
// 返回一个类型比较器,用于比较对象对是否相等
TypeComparator getTypeComparator();
// 返回一个操作符重载器,该操作符重载器可能支持多个标准类型集之间的数学操作。
OperatorOverloader getOperatorOverloader();
// 将此评估上下文中的命名变量设置为指定值。
void setVariable(String name, @Nullable Object value);
// 在此求值评估上下文中查找指定变量。
@Nullable
Object lookupVariable(String name);
}
比如:
注意:无法直接通过setVariable方法修改评估上下文中变量的属性值。
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Date;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testVariableExpression() {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
User user = new User("张三", 18);
// 设置变量(变量名,值)
context.setVariable("name", "路人甲");// 如果之前没有定义这个变量,那么你可以理解为初始化变量
context.setVariable("lesson", "Spring系列");
context.setVariable("name", "快乐向前冲");// 如果之前有定义这个变量,那么你可以理解为修改变量
context.setVariable("user111",user);
context.setVariable("user111.name","lisi");
//获取name变量,lesson变量
String name = parser.parseExpression("#name").getValue(context, String.class);
System.out.println(name);
String lesson = parser.parseExpression("#lesson").getValue(context, String.class);
System.out.println(lesson);
User u = parser.parseExpression("#user111").getValue(context, User.class);
System.out.println(u);// 无法直接通过setVariable方法修改变量的属性的值。
}
}
class User {
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("setName方法执行了");
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
结果:
Expression的setValue方法和EvaluationContext的setVariable方法
方法声明
Expression的setValue方法一共有下面几种声明:
java
void setValue(@Nullable Object var1, @Nullable Object var2) throws EvaluationException;
void setValue(EvaluationContext var1, @Nullable Object var2) throws EvaluationException;
void setValue(EvaluationContext var1, @Nullable Object var2, @Nullable Object var3) throws EvaluationException;
EvaluationContext的setVariable只有一个声明:
java
void setVariable(String var1, @Nullable Object var2);
setValue方法中写根对象和要改变的值的话,会用表达式根对象的set方法
例子1:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.util.Date;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
Date d = new Date();
Expression expression = parser.parseExpression("date");
//设日期为1号 此处为date,其原理是通过调用Date类的setDate方法,必须指定是set开头的方法
expression.setValue(d, 1);// 相当于执行"d.setDate(1)"
Object value = expression.getValue(d);
System.out.println(value);
//其原理是通过调用Date类的setYear方法
expression = parser.parseExpression("year");
expression.setValue(d, 2023);// 相当于执行"d.setYear(2023)"
value = expression.getValue(d);
System.out.println(value);
}
}
结果:
java
1
2023
例子2:把例子1中表达式的date和year写成#root.date和#root.year也是一样的效果。因为"#root."是可以省略的,上面的写法就是省略的写法。
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.util.Date;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
Date d = new Date();
Expression expression = parser.parseExpression("#root.date");
//设日期为1号 此处为date,其原理是通过调用Date类的setDate方法,必须指定是set开头的方法
expression.setValue(d, 1);// 相当于执行"d.setDate(1)"
Object value = expression.getValue(d);
System.out.println(value);
//其原理是通过调用Date类的setYear方法
expression = parser.parseExpression("#root.year");
expression.setValue(d, 2023);// 相当于执行"d.setYear(2023)"
value = expression.getValue(d);
System.out.println(value);
}
}
结果:
java
1
2023
使用setValue方法对评估上下文中的变量进行修改,相当于是赋值(表达式只写变量,而不是写变量的属性)
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Date;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
Date d = new Date();
System.out.println("原来的date是:" + d);
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("date1", d);
Expression expression = parser.parseExpression("#date1");
System.out.println("执行setValue前的date1的值:"+expression.getValue(context));
// 直接修改变量的值,相当于是赋值,相当于#date1=d;然后进行#date1=new Date(1234141)
expression.setValue(context, new Date(1234141));
Object value = expression.getValue(context);// 不指定Class就返回Object
System.out.println("执行setValue后的date1的值:"+value);
}
}
结果:
java
原来的date是:Tue Jul 02 23:41:27 CST 2024
执行setValue前的date1的值:Tue Jul 02 23:41:27 CST 2024
执行setValue后的date1的值:Thu Jan 01 08:20:34 CST 1970
使用setValue方法对评估上下文中变量的属性赋值,相当于是执行set方法
例子:
比如,下面的expression.setValue(context, 1);就是设置context这个评估上下文中的#date1.date变量的值为1,底层就是调用这个context中的date1对象的setDate(1)方法对变量进行赋值。
这里的setValue第一个参数是评估上下文对象。
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Date;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
Date d = new Date();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("date1", d);
Expression expression = parser.parseExpression("#date1.date");
//设日期为1号 此处为date,其原理是通过调用Date类的setDate方法,必须指定是set开头的方法
expression.setValue(context, 1);// 执行date1对应的对象的setDate方法。相当于执行"d.setDate(1)"。
Object value = expression.getValue(context);// 不指定Class就返回Object
System.out.println(value);
}
}
输出:
java
1
例子:
setValue方法赋值是给Expression对象对应的表达式赋值的,而不是给表达式中某个变量赋值的。setVariable是给某个变量赋值,不用依托于表达式。
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Date;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
// 例子1:
ExpressionParser parser = new SpelExpressionParser();
User user = new User();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("user", user);// 给某个变量赋值,不用依托于表达式
parser.parseExpression("#user.name").setValue(context, "小王");// 是给#user.name赋值
System.out.println(user.getName());
}
}
class User{
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("setName方法执行");
this.name = name;
}
public Integer getAge() {
return age;
}
// public void setAge(Integer age) {
// this.age = age;
// }
}
结果:
setValue方法对list和map修改,你也可以理解为赋值。但是list只能给存在的赋值,map可以给不存在的赋值,相当于添加
例子:为评估上下文中的list中的元素赋值。
集合、map的setValue不是用setXX方法来赋值了。你看Expression expression = parser.parseExpression("#list[0]");,它会找哪个setXX方法来赋值呢?反正我看不出来。但是下面这样是可以进行赋值的。
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
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.Date;
import java.util.List;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
List<Integer> list = new ArrayList<Integer>(1);
list.add(0);//添加一个元素0
EvaluationContext context = new StandardEvaluationContext();
//添加变量以方便表达式访问
context.setVariable("list", list);
//设置第一个元素的值为1
Expression expression = parser.parseExpression("#list[0]");
expression.setValue(context, 99);// 指定评估上下文,这种就相当于是用表达式在这个评估上下文中的对象对应的方法去执行set。
int first = (Integer) expression.getValue(context);
System.out.println(first);
}
}
结果:
java
99
例子5:map也是和list一样的。我也不知道它会调用哪个setXX方法,反正可以赋值就是了。
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
Map<String, Integer> map = new HashMap<>();
EvaluationContext context = new StandardEvaluationContext();
//添加变量以方便表达式访问
context.setVariable("map11", map);
//设置第一个元素的值为1
Expression expression = parser.parseExpression("#map11['key1']");
expression.setValue(context, 1);
int first = (Integer) expression.getValue(context);
System.out.println(first);
System.out.println("原来的map:"+map);
}
}
结果:
java
1
原来的map:{key1=1}
注意哈:map赋值是,键可以先不存在,你setValue就相当于是添加这个键对应的值。但是list使用setValue赋值,索引必须要先存在。
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.*;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
Map<String, Integer> map = new HashMap<>();
map.put("key999",909090909);
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("map11", map);
//map去setValue原来不存在的键对应的值
Expression expression = parser.parseExpression("#map11['key1']");
expression.setValue(context, 1);
//map去setValue原来存在的键对应的值
expression = parser.parseExpression("#map11['key999']");
expression.setValue(context, 99);
System.out.println("原来的map:"+map);
// 索引存在改变值
List<Integer> list = new ArrayList<Integer>(1);
list.add(0);
list.add(241);
context.setVariable("list11", list);
expression = parser.parseExpression("#list11[0]");
expression.setValue(context, 66);
System.out.println("改变后原来的list:"+list);
// 索引不存在改变值会报错
expression = parser.parseExpression("#list11[2]");
expression.setValue(context, 111);
System.out.println("再次改变后原来的list:"+list);
}
}
结果:
setValue修改如果是引用会改变原来的值
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Date;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
// 例子1:
ExpressionParser parser = new SpelExpressionParser();
Date date = new Date();
date.setMonth(1);
EvaluationContext context = new StandardEvaluationContext(date);
parser.parseExpression("#root.month").setValue(context, 7);
System.out.println("date的月份是:"+date.getMonth());
System.out.println("根对象是:"+context.getRootObject());
// Expression的setValue方法不能声明变量
System.out.println(context.lookupVariable("root"));// 看到这个方法不能看到根对象的值。
System.out.println(context.lookupVariable("month"));// 当然也没有这个变量。只有setVariable的才是变量。根对象,可能不是严格意义上的变量吧。
// EvaluationContext的setVariable方法可以声明变量
context.setVariable("aa", 1234);
System.out.println(context.lookupVariable("aa"));// EvaluationContext的lookupVariable方法查看根对象中变量的值
// 例子2:
User user = new User();
EvaluationContext context2 = new StandardEvaluationContext(user);
parser.parseExpression("#root.name").setValue(context2, "张三");
// parser.parseExpression("#root.age").setValue(context2, 12);// 这个会报错,因为没有setAge方法
System.out.println("user的name是:"+user.getName());// 看到原来对象的值变化了
System.out.println("user的age是:"+user.getAge());
}
}
class User{
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("setName方法执行");
this.name = name;
}
public Integer getAge() {
return age;
}
// public void setAge(Integer age) {
// this.age = age;
// }
}
结果:
java
date的月份是:7
根对象是:TypedValue: 'Fri Aug 02 23:52:48 CST 2024' of [java.util.Date]
null
null
1234
setName方法执行
user的name是:张三
user的age是:null
结论:Expression的setValue方法会改变这个表达式对应的原来对象的值。#root可能不是严格意义上的变量,反正和setVariable方法声明出来的变量是不一样的。
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Date;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
User user = new User();
EvaluationContext context2 = new StandardEvaluationContext(user);
parser.parseExpression("#root.name").setValue(context2, "张三");
// parser.parseExpression("#root.age").setValue(context2, 12);// 这个会报错,因为没有setAge方法
System.out.println("user的name是:"+user.getName());
System.out.println("user的age是:"+user.getAge());
}
}
class User{
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("setName方法执行");
this.name = name;
}
public Integer getAge() {
return age;
}
// public void setAge(Integer age) {
// this.age = age;
// }
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
结果:
java
setName方法执行
user的name是:张三
user的age是:null
setVariable只能改变变量的值,不能改变变量中属性的值,再次setVariable只是改变变量的绑定的引用而已,所以setVariable怎么都无法影响原来对象的值
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Date;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
User user = new User();
EvaluationContext context = new StandardEvaluationContext(user);
context.setVariable("aa", user);
context.setVariable("aa", new User("lisi",12));
System.out.println(parser.parseExpression("#aa").getValue(context));// 看到变量的值改变了
context.setVariable("aa.name", "王五");
System.out.println(parser.parseExpression("#aa").getValue(context));// 但是无法改变变量的属性值
System.out.println(user);// setVariable不会改变原来的变量对应的对象的值
}
}
class User{
private String name;
private Integer age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
结果:
java
User{name='lisi', age=12}
User{name='lisi', age=12}
User{name='null', age=null}
EvaluationContext的setVariable方法是设置变量到评估上下文中的,getValue是会先把变量替换然后再计算的
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("age", 4);// setVariable是用来设置变量到评估上下文中的
System.out.println(parser.parseExpression("54+#age+'hello'").getValue(context, String.class));// #age可以去评估上下文对象中取变量的值替换#age,然后再进行计算。
}
}
输出:
java
58hello
Expression的getValue方法是通过调用get方法来执行拿到值的(前提是这个要拿的值是一个变量的属性才会调用get方法拿值,如果只是拿一个变量,那么没有什么get方法),带#的getValue就是取变量,不带#号的就是取根对象的属性或者方法。如果括号里面没有参数,可能方法的括号可以不写,有参数得写。
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
User user = new User();
user.setName("张三");
EvaluationContext context = new StandardEvaluationContext(user);
System.out.println("1、"+parser.parseExpression("name").getValue(context, String.class));
System.out.println("2、"+parser.parseExpression("#root.name").getValue(context, String.class));// 和上面这个写法等价,因为#root可以省略
System.out.println("3、"+parser.parseExpression("getName()").getValue(context, String.class));
System.out.println("4、"+parser.parseExpression("#root.getName()").getValue(context, String.class));// 和上面这个写法等价,因为#root可以省略
System.out.println("5、"+parser.parseExpression("#root.getName").getValue(context, String.class));// 如果括号里面没有参数,可能方法的括号可以不写,有参数得写
context.setVariable("aaa", "12314");
System.out.println("6、"+parser.parseExpression("#aaa").getValue(context, String.class));// 这样就不是调用get方法了
context.setVariable("name", "李小龙");
System.out.println("7、"+parser.parseExpression("#name").getValue(context, String.class));// 注意和上面的System.out.println(parser.parseExpression("name").getValue(context, String.class));不一样,使用#就是获取变量
System.out.println("8、"+parser.parseExpression("#root.getName2()").getValue(context, String.class));// 有参数得写
}
}
class User {
private String name;
private Integer age;
public String getName() {
System.out.println("getName方法执行");
return name;
}
public String getName2(String s) {
System.out.println("getName2方法执行");
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
// public void setAge(Integer age) {
// this.age = age;
// }
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
结果:
lookupVariable方法
注意:EvaluationContext的lookupVariable方法和Expression的getValue方法其实是差不多的。只是,EvaluationContext的lookupVariable方法中的表达式不用写#号,还有就是返回值是不一样的。
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
User user = new User();
user.setName("张三");
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable("oldUser", user);
System.out.println(context2.lookupVariable("oldUser"));// lookupVariable方法和getValue方法效果其实是一样的。只是getValue方法返回的不是一个Object的值。
System.out.println(parser.parseExpression("#oldUser").getValue(context2, User.class));
System.out.println(parser.parseExpression("#oldUser.name").getValue(context2, String.class));
}
}
class User {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("setName方法执行");
this.name = name;
}
public Integer getAge() {
return age;
}
// public void setAge(Integer age) {
// this.age = age;
// }
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
输出:
java
setName方法执行
User{name='张三', age=null}
User{name='张三', age=null}
张三
setValue方法不能改变根对象
例子1:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.util.Date;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
Date d = new Date();
Expression expression = parser.parseExpression("#root");
expression.setValue(d, "测试改变根对象");
Object value = expression.getValue(d);
System.out.println(value);
}
}
结果:
java
Tue Jul 02 23:31:56 CST 2024
例子2:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Date;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
ExpressionParser parser = new SpelExpressionParser();
Date d = new Date();
EvaluationContext context = new StandardEvaluationContext(d);
Expression expression = parser.parseExpression("#root");
System.out.println("执行setValue前的date的值:"+expression.getValue(context));
// 改变根对象的值
expression.setValue(context, new Date(1234141));
Object value = expression.getValue(context);// 不指定Class就返回Object
System.out.println("执行setValue后的date的值:"+value);// 发现改变不了
}
}
结果:
java
执行setValue前的date的值:Sat Jul 06 10:18:41 CST 2024
执行setValue后的date的值:Sat Jul 06 10:18:41 CST 2024
EL表达式的语法
基本表达式
字面量表达式
解析器会自动把表达式字符串解析对应的值,即它能认识这些字母量,会被当作某种类型的值。注意:字符串要加单引号。getValue指定Class,可以返回对应的类型。
SpEL支持的字面量包括:字符串、数字类型(int、long、float、double)、布尔类型、null类型。
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test2() {
ExpressionParser parser = new SpelExpressionParser();
String str1 = parser.parseExpression("'Hello World!'").getValue(String.class);
int int1 = parser.parseExpression("1").getValue(Integer.class);
long long1 = parser.parseExpression("-1L").getValue(long.class);
float float1 = parser.parseExpression("1.1").getValue(Float.class);
double double1 = parser.parseExpression("1.1E+2").getValue(double.class);
int hex1 = parser.parseExpression("0xa").getValue(Integer.class);
long hex2 = parser.parseExpression("0xaL").getValue(long.class);
boolean true1 = parser.parseExpression("true").getValue(boolean.class);
boolean false1 = parser.parseExpression("false").getValue(boolean.class);
Object null1 = parser.parseExpression("null").getValue(Object.class);
System.out.println("str1=" + str1);
System.out.println("int1=" + int1);
System.out.println("long1=" + long1);
System.out.println("float1=" + float1);
System.out.println("double1=" + double1);
System.out.println("hex1=" + hex1);
System.out.println("hex2=" + hex2);
System.out.println("true1=" + true1);
System.out.println("false1=" + false1);
System.out.println("null1=" + null1);
}
}
结果:
java
str1=Hello World!
int1=1
long1=-1
float1=1.1
double1=110.0
hex1=10
hex2=10
true1=true
false1=false
null1=null
算数运算表达式
解析器会自动把表达式中的算术运算进行解析,让解析结果符合算术运算的结果。
SpEL支持加(+)、减(-)、乘(*)、除(/)、求余(%)、幂(^)运算。
SpEL还提供余(MOD)和除(DIV)两个运算符,与"%"和"/"等价,不区分大小写。
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test3() {
ExpressionParser parser = new SpelExpressionParser();
// 加减乘除
int result1 = parser.parseExpression("1+2-3*4/2").getValue(Integer.class);
// 求余
int result2 = parser.parseExpression("4%3").getValue(Integer.class);
// 幂运算
int result3 = parser.parseExpression("2^3").getValue(Integer.class);
System.out.println("result1=" + result1 + ",result2=" + result2 + ",result3=" + result3);
// 求余(大小写不会被区分的,但是注意,不能写为parser.parseExpression("4moD3").getValue(Integer.class);,即中间得有空格才行)
int result4 = parser.parseExpression("4 moD 3").getValue(Integer.class);
// 除
double result5 = parser.parseExpression("9.0 dIv 2").getValue(Double.class);
// 除,如果是9除2,那么结果就是4.0,和java中一样:整数相除会进行整数除法,结果会向下取整,即舍弃小数部分,只保留整数部分的结果。
double result6 = parser.parseExpression("9 dIv 2").getValue(Double.class);
System.out.println("result4=" + result4 + ",result5=" + result5 + ",result6=" + result6);
}
}
结果:
java
result1=-3,result2=1,result3=8
result4=1,result5=4.5,result6=4.0
关系表达式
解析器会自动把表达式中的关系运算进行解析。注意:getValue的类型可以指定为boolean.class的,不是只有引用类型的class。
SpEL表达式支持的关系运算符:等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=),区间(between)运算。
如parser.parseExpression("1>2").getValue(boolean.class);
将返回false;
而parser.parseExpression("1 between {1, 2}").getValue(boolean.class);
将返回true。
注意:
between运算符右边操作数必须是列表类型(在SpEL中,列表类型可以使用花括号 {} 来表示,其中元素之间使用逗号分隔。),且只能包含2个元素。第一个元素为开始,第二个元素为结束。并且区间运算是包含边界值的,即等价于 xxx>=list.get(0) && xxx<=list.get(1)
。
SpEL同样提供了"EQ" 、"NE"、 "GT"、"GE"、 "LT" 、"LE"来表示等于、不等于、大于、大于等于、小于、小于等于,不区分大小写。
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test4() {
ExpressionParser parser = new SpelExpressionParser();
boolean v1 = parser.parseExpression("1>2").getValue(boolean.class);
boolean between1 = parser.parseExpression("1 between {1,2}").getValue(boolean.class);
System.out.println("v1=" + v1);
System.out.println("between1=" + between1);
boolean v2 = parser.parseExpression("1 GT 2").getValue(boolean.class);
System.out.println("v2=" + v2);
}
}
结果:
java
v1=false
between1=true
v2=false
逻辑表达式
解析器会自动把表达式中的逻辑符号进行解析。
且(and或者&&)、或(or或者||)、非(!或NOT)。
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test5() {
ExpressionParser parser = new SpelExpressionParser();
boolean result1 = parser.parseExpression("2>1 and (!true or !false)").getValue(boolean.class);
boolean result2 = parser.parseExpression("2>1 && (!true || !false)").getValue(boolean.class);
boolean result3 = parser.parseExpression("2>1 and (NOT true or NOT false)").getValue(boolean.class);
boolean result4 = parser.parseExpression("2>1 && (NOT true || NOT false)").getValue(boolean.class);
System.out.println("result1=" + result1);
System.out.println("result2=" + result2);
System.out.println("result3=" + result3);
System.out.println("result4=" + result4);
}
}
结果:
java
result1=true
result2=true
result3=true
result4=true
字符串连接及截取表达式
使用+
进行字符串连接,使用'字符串'[下标]
来截取一个字符,目前只支持截取一个字符。但是可以使用subString方法来获取多个字符,反正SpEL表达式中的字符串都可以调用java中的方法的。
例子1:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test6() {
ExpressionParser parser = new SpelExpressionParser();
String result1 = parser.parseExpression("'hello world'[2]").getValue(String.class);
System.out.println("result1=" + result1);
}
}
结果:
java
result1=l
例子2:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test6() {
ExpressionParser parser = new SpelExpressionParser();
String result1 = parser.parseExpression("'hello world'.substring(1, 3)").getValue(String.class);
System.out.println("result1=" + result1);
}
}
结果:
java
result1=el
例子3:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test6() {
ExpressionParser parser = new SpelExpressionParser();
// 字符串拼接
String result1 = parser.parseExpression("'hello w'+'orld'").getValue(String.class);
System.out.println("result1=" + result1);
}
}
输出:
java
result1=hello world
三目运算
表达式中的三目运算符是可以被解析的。
三目运算符 **"表达式1?表达式2:表达式3"**用于构造三目运算表达式。
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test6() {
ExpressionParser parser = new SpelExpressionParser();
String result1 = parser.parseExpression("2>1?'大于':'小于'").getValue(String.class);
System.out.println("result1=" + result1);
}
}
结果:
java
result1=大于
Elivis(埃尔维斯) 运算符
Elivis运算符**"表达式1?:表达式2"**从Groovy语言引入用于简化三目运算符的,当表达式1为非null时,则返回表达式1,当表达式1为null时则返回表达式2,简化了三目运算符方式"表达式1? 表达式1:表达式2",如"null?:false"将返回false,而"true?:false"将返回true;
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test6() {
ExpressionParser parser = new SpelExpressionParser();
boolean result1 = parser.parseExpression("null?:false").getValue(Boolean.class);
System.out.println("result1=" + result1);
}
}
结果:
java
result1=false
正则表达式
SpEL表达式中也支持使用正则表达式的写法
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test6() {
ExpressionParser parser = new SpelExpressionParser();
// \d 可以匹配任意一个十进制数字,即 0 到 9 之间的任何一个数字.而 \d{3} 则表示匹配连续出现三次数字字符的模式。换句话说,\d{3} 表示正则表达式需要匹配一个由三个连续数字字符构成的子串。注意:正则表达式要用matches关键字,并且匹配规则要用引号括起来。
boolean result1 = parser.parseExpression("'abc123test' matches '\\d{3}'").getValue(Boolean.class);
System.out.println("result1=" + result1);
}
}
结果:
java
result1=false
括号优先级表达式
可以使用括号,在括号内的表达式会优先被执行。
比如:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test6() {
ExpressionParser parser = new SpelExpressionParser();
double result1 = parser.parseExpression("7*((5+5)*2%3)").getValue(Double.class);
System.out.println("result1=" + result1);
}
}
结果:
java
result1=14.0
类相关表达式
类相关的Class对象、静态变量、静态方法的访问
使用"T(Type)"来表示java.lang.Class实例,"Type"必须是类全限定名,"java.lang"包除外,即该包下的类可以不指定包名;使用类类型表达式还可以进行访问类静态方法及类静态字段。
例子:
java
package com.example.springbootdemo;
import com.example.springbootdemo.service.impl.BookServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
public static final String EXPRESSION = "test";
@Test
public void testClassTypeExpression() {
ExpressionParser parser = new SpelExpressionParser();
//java.lang包类访问
Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class);
System.out.println(result1);
//其他包类访问
String expression2 = "T(com.example.springbootdemo.service.impl.BookServiceImpl)";
Class<BookServiceImpl> value = parser.parseExpression(expression2).getValue(Class.class);
System.out.println(value == BookServiceImpl.class);
//类静态字段访问
int result3 = parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class);
System.out.println(result3);
//类静态方法调用
int result4 = parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class);
System.out.println(result4);
//非java.lang包下的类的静态属性访问
String result5 = parser.parseExpression("T(com.example.springbootdemo.SpringbootDemoApplicationTests).EXPRESSION").getValue(String.class);
System.out.println(result5);
}
}
结果:
java
class java.lang.String
true
2147483647
1
test
对于java.lang包里的可以直接使用"T(String)"访问;其他包必须用类全限定名来访问;T(XX)返回的结果就是XX的Class对象。T(xx1).xx2(xx3)相当于调用括号中代表的xx1类的xx2方法的返回值,并且xx2的参数是xx3。
可以进行静态字段访问如"T(Integer).MAX_VALUE";也可以进行静态方法访问如"T(Integer).parseInt('1')"。
类实例化
类实例化同样使用java关键字"new",类名必须是全限定名,但java.lang包内的类型除外,如String、Integer。
java
package com.example.springbootdemo;
import com.example.springbootdemo.service.impl.BookServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.util.Date;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testConstructorExpression() {
ExpressionParser parser = new SpelExpressionParser();
String result1 = parser.parseExpression("new String('路人甲java')").getValue(String.class);
System.out.println(result1);
Date result2 = parser.parseExpression("new java.util.Date()").getValue(Date.class);
System.out.println(result2);
}
}
结果:
java
路人甲java
Wed Jun 12 23:47:31 CST 2024
instanceof表达式
SpEL支持instanceof运算符,跟Java内使用同义;如"'haha' instanceof T(String)"将返回true。
例子1:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testInstanceOfExpression() {
ExpressionParser parser = new SpelExpressionParser();
Boolean value = parser.parseExpression("'路人甲' instanceof T(String)").getValue(Boolean.class);
System.out.println(value);
}
}
输出:
java
true
例子2:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testClassTypeExpression() {
ExpressionParser parser = new SpelExpressionParser();
String expression = "new com.example.springbootdemo.service.impl.BookServiceImpl() instanceof T(com.example.springbootdemo.service.impl.BookServiceImpl)";
Boolean value = parser.parseExpression(expression).getValue(Boolean.class);
System.out.println(value);
}
}
结果:
java
true
变量定义及修改
在EvaluationContext
中使用setVariable()
方法设置变量,在表达式中使用#变量名
来获取变量的值, 变量名的命名规范遵循Java语言变量名的命名规范,即小驼峰命名法。
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testVariableExpression() {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
// 设置变量(变量名,值)
context.setVariable("name", "路人甲java");// 如果之前没有定义这个变量,那么你可以理解为初始化变量
context.setVariable("lesson", "Spring系列");
context.setVariable("name", "快乐向前冲");// 如果之前有定义这个变量,那么你可以理解为修改变量
//获取name变量,lesson变量
String name = parser.parseExpression("#name").getValue(context, String.class);
System.out.println(name);
String lesson = parser.parseExpression("#lesson").getValue(context, String.class);
System.out.println(lesson);
}
}
结果:
java
快乐向前冲
Spring系列
除了引用自定义变量,SpEL表达式还支持两个特殊的变量 #this
和#root
。#this
变量总是被定义并且指代当前正在评估的对象。#root
变量总是被定义并且指代根对象。
活动上下文变量#this的使用案例:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testVariableExpression() {
// 创建一个素数列表。
List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17);
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
// 设置变量(变量名,值)
context.setVariable("primes", primes);
// 从列表中选择所有>10的素数。#primes表示访问定义的属性。#this表示
// 在SpEL(Spring表达式语言)中,".?[选择表达式]"一起表示过滤器,使用.?[选择表达式]可以方便的对数组、集合、字典进行过滤,类似于Java Stream的filter方法。
// 在这个特定的语法中,"."用于访问集合或对象的属性,而"?[]"内写过滤条件。因此,#primes.?[#this > 10]表示对变量primes中的列表进行过滤,只保留大于10的元素。#this表示正在遍历进行判断的元素。这个语法可以在SpEL中用来快速对集合进行筛选。
String expression = "#primes.?[#this > 10]";
// 评估到满足规则的素数列表。即[11, 13, 17].
List<Integer> primesGreaterThanTen =
parser.parseExpression(expression).getValue(context, List.class);
System.out.println(primesGreaterThanTen);
}
}
结果:
java
[11, 13, 17]
根变量#root的使用案例:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testVariableExpression() {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
User user = new User("张三", 20);
// 使用#root访问根对象及其属性
String expression = "#root.name + #root.age + '岁了.'";
// 第二个参数表示设置根对象。
String message = parser.parseExpression(expression)
.getValue(context, user, String.class);
System.out.println(message);
}
}
class User{
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
结果:
java
张三20岁了.
#root和#this一起使用:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testVariableExpression() {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
List<String> hobbies = Arrays.asList("篮球", "游泳");
User user = new User("张三", 20, hobbies);
// 使用#root来访问根对象属性,使用#this来正在遍历的元素。"集合.![#this+'♥']"相当于是把集合中的每一个元素都加上♥,组成一个新的元素,放到一个集合中去。注意:#root.hobbies.![#this+'♥']整体会返回一个集合。
String expression = "#root.name + '今年' + #root.age + '岁了。爱好:' + #root.hobbies.![#this+'♥']";
String message = parser.parseExpression(expression).getValue(context, user, String.class);
System.out.println(message);
}
}
class User {
private String name;
private Integer age;
private List<String> hobbies;
public User(String name, Integer age, List<String> hobbies) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
}
结果:
java
张三今年20岁了。爱好:篮球♥,游泳♥
一般情况下,#this都是和![]或者?[]一起使用的。不是![]或者?[]一起使用的情况下,#this一般都是指根对象。
比如:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testVariableExpression() {
ExpressionParser parser = new SpelExpressionParser();
List<String> hobbies = Arrays.asList("篮球", "游泳");
User user = new User("张三", 20, hobbies);
EvaluationContext context = new StandardEvaluationContext(user);// 这里写user,也是相当于是指定根对象。
// 使用#root来访问根对象属性,使用#this来访问当前对象属性
String expression = "'根对象:'+#root.name +'。活动上下文对象:'+#this.name";
String message = parser.parseExpression(expression).getValue(context, String.class);
System.out.println(message);
}
}
class User {
private String name;
private Integer age;
private List<String> hobbies;
public User(String name, Integer age, List<String> hobbies) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
}
结果:
java
根对象:张三。活动上下文对象:张三
自定义函数
目前只支持将类静态方法注册为自定义函数;
SpEL可以使用StandardEvaluationContext的registerFunction方法注册自定义的函数。但其实完全可以使用setVariable代替,两者其实本质是一样的。效果是一样的。
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testFunctionExpression() throws SecurityException, NoSuchMethodException {
// 定义函数,使用registerFunction和setVariable都可以,不过从语义上面来看用registerFunction更恰当
StandardEvaluationContext context = new StandardEvaluationContext();
// 获取com.example.springbootdemo.SpringbootDemoApplicationTests.class.getDeclaredMethod的add方法,并且add方法的两个参数分别是Integer和String
Method method1 = com.example.springbootdemo.SpringbootDemoApplicationTests.class.getDeclaredMethod("add", Integer.class, String.class);
// 使用registerFunction注册方法,这里取一个SpEL表达式中的键名叫add1
context.registerFunction("add1", method1);
// 获取com.example.springbootdemo.SpringbootDemoApplicationTests.class.getDeclaredMethod的add方法,并且add方法的两个参数都是Integer
Method method2 = com.example.springbootdemo.SpringbootDemoApplicationTests.class.getDeclaredMethod("add", Integer.class, Integer.class);
// 使用setVariable注册方法,这里取一个SpEL表达式中的键名叫add2
context.setVariable("add2", method2);
ExpressionParser parser = new SpelExpressionParser();
System.out.println("使用registerFunction的做法:"+parser.parseExpression("#add1(3,7)").getValue(context, String.class));
System.out.println("使用setVariable的做法:"+parser.parseExpression("#add2(3,7)").getValue(context, String.class));
}
static String add(Integer num1,Integer num2) {
System.out.println("add(Integer num1,Integer num2)方法执行了");
return "结果是:"+(num1+num2);
}
static String add(Integer num1,String num2) {
System.out.println("add(Integer num1,String num2)方法执行了");
return "结果是:"+(num1+num2);
}
}
结果:
java
add(Integer num1,String num2)方法执行了
使用registerFunction的做法:结果是:37
add(Integer num1,Integer num2)方法执行了
使用setVariable的做法:结果是:10
此处可以看出"registerFunction"和"setVariable"都可以注册自定义函数,但是两个方法的含义不一样,推荐使用"registerFunction"方法注册自定义函数,因为从名字上看更合适。
表达式赋值
setValue方法实现赋值:
使用Expression#setValue
方法可以给表达式赋值。可以给根变量的属性、普通变量、普通变量的属性赋值,活动上下文一般不用被赋值。
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
// 这里是创建匿名内部类实例的一种写法,这里只是想复习一下匿名内部类的知识而已。相当于是创建了一个匿名的内部类,这个匿名内部类继承自User类。并且匿名内部类添加自己的额外的属性name,并提供自己的一些方法。这里这样写声明的时候就已经创建了这个类的实例了。
User user = new User() {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "$classname{" +
"name='" + name + '\'' +
'}';
}
};
// 这里写两个块,是为了可以使用两个一样的变量,现在作用域而已,并且两个块内都可以访问这个局部变量。
{
// 改变根对象的属性
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext(user);
parser.parseExpression("#root.age").setValue(context, "18");
System.out.println("用户的年龄是:"+user.getAge());
parser.parseExpression("#root.name").setValue(context, "路人甲java1");
// 下面这个表达式是会报错,因为上面的匿名内部类实例声明的是User类型。编译看左边,编译器表面上可以认为这个user是User类型的,没有getName()方法,所以编译不通过。
// System.out.println(user.getName());// 会报错,编译不通过
// 但是下面这种写法就可以,因为这个不会被编译器检查。
System.out.println(parser.parseExpression("#root.name").getValue(context, String.class));
}
{
// 改变变量和变量的属性
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("user", user);// 因为这里的user因为被上一个块的setValue赋值了,所以这里拿到的user,内部的name不是null,age不是0。
System.out.println(parser.parseExpression("#user.age").getValue(context));
System.out.println(parser.parseExpression("#user.name").getValue(context));
// 我们改变这个变量的值。即,改变这个变量的指向
parser.parseExpression("#user").setValue(context, new User());
// 看看是不是改变了指向,如果改不了了,那么这里的age是0
System.out.println(parser.parseExpression("#user.age").getValue(context));
// 看看原来对象的值有没有变化
System.out.println(user);// 看到原来的对象的值没有被改变。因为这里只是改变变量指向的引用而已。所以不会影响原来对象的。
// 改变这个变量的属性
parser.parseExpression("#user.age").setValue(context, 99);
// 看看改变后的属性
System.out.println(parser.parseExpression("#user.age").getValue(context));
}
}
}
class User{
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
结果:
java
用户的年龄是:18
路人甲java1
18
路人甲java1
0
$classname{name='路人甲java1'}
99
setValue和其他方式实现赋值:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.List;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testAssignExpression1() {
String s1="原来的s1";
String s2="原来的s2";
String s3="原来的s3";
User user=new User("张三",18);
ExpressionParser ep= new SpelExpressionParser();
//创建上下文变量
EvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable("name", s1);// 初始化name的值为s1
ctx.setVariable("name", s2);// 改变引用
System.out.println("1、使用setVariable改变上下文中name的值为s2后,现在上下文中的name值为:"+ep.parseExpression("#name").getValue(ctx));
System.out.println("2、setVariable只是改变了评估上下文中这个name这个键对应的引用值而已,相当于是改变了指向。让指向变为s2了。所以原来的s1值没有变化。所以原来的s1的值是:"+s1);
ep.parseExpression("#name").setValue(ctx,s3);
System.out.println("3、使用setValue方法改变值,但是表达式写的是直接一个变量,那么和上面的setVariable效果一样,等价于赋值,只是改变变量对应的引用为s3了。所以原来的s2值没有变化。所以原来的s2的值是:"+s2);
System.out.println("4、使用setValue+直接变量改变上下文中的值为s3后,现在上下文中的name值为:"+ep.parseExpression("#name").getValue(ctx));
ep.parseExpression("#name").setValue(ctx,user);
System.out.println("5、使用setValue+直接变量改变上下文中的值为user后(原来同上),现在上下文中的name值为:"+ep.parseExpression("#name").getValue(ctx));
ep.parseExpression("#name.name").setValue(ctx,"李四");
System.out.println("6、这里的setValue+表达式写的是一个变量的一个属性,那么就是调用set方法来改变引用中的属性的值,所以会影响原来对象。原来的user的值是:"+user);
System.out.println("7、使用setValue+表达式写的是一个变量的一个属性改变上下文中的值为name指向的user的name属性后,现在上下文中的name值为:"+ep.parseExpression("#name").getValue(ctx));
System.out.println("8、使用等于也可以赋值改变引用,但是不是通过set方法的,是通过反射直接访问属性的,并且可以忽略修饰符private进行修改。注意,这种赋值的getValue解析结果就是赋的值哈,这里的getValue方法相当于是把100赋给name执行的user的age,并且返回100:"+ep.parseExpression("#name.age=100").getValue(ctx));
System.out.println("9、看到等于也可以改变原来对象的值的:"+user);
System.out.println("10、等于也可以直接把一个上下文中的变量指向其他的引用,不止可以改变原来变量的属性:"+ep.parseExpression("#name='但是,注意这个也要借助表达式才行的'").getValue(ctx));
}
}
class User {
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
System.out.println("getName方法被调用了");
return name;
}
public void setName(String name) {
System.out.println("setName方法被调用了");
this.name = name;
}
public Integer getAge() {
System.out.println("getAge方法被调用了");
return age;
}
public void setAge(Integer age) {
System.out.println("setAge方法被调用了");
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
结果:
java
1、使用setVariable改变上下文中name的值为s2后,现在上下文中的name值为:原来的s2
2、setVariable只是改变了评估上下文中这个name这个键对应的引用值而已,相当于是改变了指向。让指向变为s2了。所以原来的s1值没有变化。所以原来的s1的值是:原来的s1
3、使用setValue方法改变值,但是表达式写的是直接一个变量,那么和上面的setVariable效果一样,等价于赋值,只是改变变量对应的引用为s3了。所以原来的s2值没有变化。所以原来的s2的值是:原来的s2
4、使用setValue+直接变量改变上下文中的值为s3后,现在上下文中的name值为:原来的s3
5、使用setValue+直接变量改变上下文中的值为user后(原来同上),现在上下文中的name值为:User{name='张三', age=18}
setName方法被调用了
6、这里的setValue+表达式写的是一个变量的一个属性,那么就是调用set方法来改变引用中的属性的值,所以会影响原来对象。原来的user的值是:User{name='李四', age=18}
7、使用setValue+表达式写的是一个变量的一个属性改变上下文中的值为name指向的user的name属性后,现在上下文中的name值为:User{name='李四', age=18}
setAge方法被调用了
8、使用等于也可以赋值改变引用,但是不是通过set方法的,是通过反射直接访问属性的,并且可以忽略修饰符private进行修改。注意,这种赋值的getValue解析结果就是赋的值哈,这里的getValue方法相当于是把100赋给name执行的user的age,并且返回100:100
9、看到等于也可以改变原来对象的值的:User{name='李四', age=100}
10、等于也可以直接把一个上下文中的变量指向其他的引用,不止可以改变原来变量的属性:但是,注意这个也要借助表达式才行的
对象属性存取及安全导航表达式
对象属性获取非常简单,即,使用如"a.property.property"这种点缀式获取就行了。
注意:SpEL对于属性名首字母是不区分大小写的;其他位置的字母呢?答:其他位置的字母还是区分大小写的。
SpEL还引入了Groovy语言中的安全导航运算符,即:"对象?.属性"。这个写法可以用来避免"?."前边的对象为null时抛出空指针异常。这种写法可以让"?."前边的对象为null时整个表达式返回null;
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test5() {
User user = new User();
user.setName("张三");
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("user", user);
ExpressionParser parser = new SpelExpressionParser();
// 通过点缀式来获取值
System.out.println("用户的名字是:" + parser.parseExpression("#user.name").getValue(context, String.class));
// 使用赋值表达式修改值。
parser.parseExpression("#user.name").setValue(context,"李四");
System.out.println("改后的用户名字是:" + parser.parseExpression("#user.name").getValue(context, String.class));
// 试一下看看能不能不区分大小写。
System.out.println("首字母大小写都是可以的:" + parser.parseExpression("#user.Name").getValue(context, String.class));
// 但是其他的位置还是区分大小写的
try {
System.out.println("但是其他的位置还是区分大小写的:" + parser.parseExpression("#user.NaMe").getValue(context, String.class));
} catch (EvaluationException e) {
System.out.println("出错了:" + e.getMessage());
}
// 但是使用.符号来访问user.car.name会报错,原因:user.car为null
try {
System.out.println("用户的车是:" + parser.parseExpression("#user.car.name").getValue(context, String.class));
} catch (EvaluationException e) {
System.out.println("出错了:" + e.getMessage());
}
// 但是使用安全访问符号?.,可以规避null错误,如果调用者是null,就返回null。
System.out.println("用户的车是:" + parser.parseExpression("#user?.car?.name").getValue(context, String.class));
Car car = new Car();
car.setName("保时捷");
user.setCar(car);
// 非null情况下,使用?.符号来获取值。正常就返回正常的结果就行了。
System.out.println(parser.parseExpression("#user?.car?.toString()").getValue(context, String.class));
}
}
class User {
private Car car;
private String name;
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Car {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
'}';
}
}
结果:
java
用户的名字是:张三
改后的用户名字是:李四
首字母大小写都是可以的:李四
出错了:EL1008E: Property or field 'NaMe' cannot be found on object of type 'com.example.springbootdemo.User' - maybe not public or not valid?
出错了:EL1007E: Property or field 'name' cannot be found on null
用户的车是:null
Car{name='保时捷'}
对象方法调用
对象方法调用更简单,跟Java语法一样;像字符串的方法可以直接调用java的方法。如"'haha'.substring(2,4)"将返回"ha";
对于根对象的方法,可以直接写方法,#root.
可以省略;
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test() {
// 创建一个Calculator对象
Calculator calculator = new Calculator();
// 使用SpEL表达式调用Calculator对象的add方法。根对象的方法可以直接调用。
EvaluationContext context = new StandardEvaluationContext(calculator);
ExpressionParser parser = new SpelExpressionParser();
// 根对象的方法可以直接调用的。
int result = parser.parseExpression("add(10, 20)").getValue(context, Integer.class);
// 打印调用方法后的结果
System.out.println("方法调用结果:" + result);
// 不是根对象的方法,不能直接调用。即,不能省略`#变量`
context.setVariable("car", new Car());
parser.parseExpression("#car.setName('劳斯莱斯-库里南')");// 执行了`表达式对象.getValue方法`才会执行表达式
System.out.println("车的名字是:" + parser.parseExpression("#car.getName()").getValue(context));// 所以返回是null
// 执行了`表达式对象.getValue方法`才行。才会执行对应的解析,会真的执行setName方法
parser.parseExpression("#car.setName('劳斯莱斯-库里南')").getValue(context);
System.out.println("车的名字是:" + parser.parseExpression("#car.getName()").getValue(context));
// 直接调用字符串的方法。getValue给了Class,那么返回值类型就是对应的类型了,否则getValue返回的是Object类型的对象。
System.out.println("直接调用java字符串的方法:"+parser.parseExpression("'haha'.substring(2,4)").getValue(context, String.class));
// 方法的参数是调用的方法返回值(括号内的方法会先执行)。splicing方法:将多个字符串连接在一起,形成一个新的字符串。substring方法:从索引为 0 到索引为 4(不包括)的子字符串.
System.out.println("方法的参数是调用的方法返回值:"+parser.parseExpression("splicing(#car.getName(),'haha'.substring(2,4))").getValue(context, String.class));
}
}
class Calculator {
public int add(int a, int b) {
System.out.println("执行了Calculator的add(int a, int b)方法");
return a + b;
}
public String splicing(String str1,String str2){
System.out.println("执行了Calculator的splicing(String str1,String str2)方法");
return str1+str2;
}
}
class Car {
private String name;
public String getName() {
System.out.println("执行了Car的getName()方法");
return name;
}
public void setName(String name) {
System.out.println("执行了Car的setName(String name)方法执行了");
this.name = name;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
'}';
}
}
结果:
java
执行了Calculator的add(int a, int b)方法
方法调用结果:30
执行了Car的getName()方法
车的名字是:null
执行了Car的setName(String name)方法执行了
执行了Car的getName()方法
车的名字是:劳斯莱斯-库里南
直接调用java字符串的方法:ha
执行了Car的getName()方法
执行了Calculator的splicing(String str1,String str2)方法
方法的参数是调用的方法返回值:劳斯莱斯-库里南ha
Bean引用
SpEL支持使用"@"符号来引用Bean。但是注意,评估上下文中要设置BeanResolver接口的实现才行,不然无法使用@获取Bean。
通过BeanFactory或者ConfigurableListableBeanFactory获取自动注入的bean
我们也可以拿到Spring的BeanFactory自动注入的bean。
例子:
java
package com.example.springbootdemo.service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service
* @ClassName: BookService
* @Description: TODO
* @Version 1.0
*/
public interface BookService {
public void save();
}
java
package com.example.springbootdemo.service.impl;
import com.example.springbootdemo.service.BookService;
import org.springframework.stereotype.Service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service.impl
* @ClassName: BookServiceImpl
* @Description: TODO
* @Version 1.0
*/
@Service
public class BookServiceImpl implements BookService {
@Override
public void save() {
System.out.println("book service is running ...");
}
}
java
package com.example.springbootdemo;
import com.example.springbootdemo.service.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import javax.annotation.Resource;
@SpringBootTest
class SpringbootDemoApplicationTests {
//获取到Spring容器的beanFactory对象
@Resource
private ConfigurableListableBeanFactory beanFactory;
@Test
public void test() {
SpelExpressionParser parser = new SpelExpressionParser();
// 演示1:获取原来Spring容器中的bean或者执行bean的方法
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));// 拿到Spring中的BeanFactory对象,并且告诉评估上下文
// 获取Spring中的bean
BookService bookService = parser.parseExpression("@bookServiceImpl").getValue(evaluationContext, BookService.class);
// 执行bean的方法,看看是否能执行成功
bookService.save();
// 下面这样直接调用bean的方法也行,上面是获取了bean的实例然后执行对应的方法的。
parser.parseExpression("@bookServiceImpl.save").getValue(evaluationContext);
// 演示2:自己把对象放到容器里面,然后去获取bean或者通过容器执行bean的方法
// 如果要把自己写的对象放到Spring容器中可以这样做
StandardEvaluationContext evaluationContext2 = new StandardEvaluationContext();
User user = new User();
Car car = new Car();
car.setName("保时捷");
user.setCar(car);
beanFactory.registerSingleton("user", user);// 注册一个单例的User对象到Spring容器中
evaluationContext2.setBeanResolver(new BeanFactoryResolver(beanFactory));// 在引用Bean时需要使用BeanResolver接口来查找Bean。所以需要告诉评估上下文对象使用哪个Bean工厂处理器
// 获取bean实例,看看容器中的bean和我们放进去的对象是不是一个对象。
User user1 = parser.parseExpression("@user").getValue(evaluationContext2, User.class);
System.out.println(user==user1);
// 执行对应的方法
System.out.println(parser.parseExpression("@user.getCar.getName").getValue(evaluationContext2,String.class));
}
}
class User {
private Car car;
private String name;
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"car=" + car +
", name='" + name + '\'' +
'}';
}
}
class Car {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
'}';
}
}
结果:
java
book service is running ...
book service is running ...
true
保时捷
集合相关表达式
定义List和数组(一维和多维数组)
从Spring3.0.4开始支持内联List,即,使用{表达式,......}定义内联List。比如"{1,2,3}"将返回一个整型的ArrayList,而"{}"将返回空的,即长度为0的List。
注意:在SpEL表达式字面量中定义的List,如果list中的元素都是纯字面量,那么是不能被修改的,因为SpEL中使用了java.util.Collections.unmodifiableList方法会将你这样定义的List设置为不可修改的了。但是只要有一个元素不是纯字面量,那么就可以修改。
比如:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test7() {
ExpressionParser parser = new SpelExpressionParser();
//将返回不可修改的空List
List<Integer> result2 = parser.parseExpression("{}").getValue(List.class);
System.out.println("result2的长度为:"+result2.size());
//对于字面量列表也将返回不可修改的List
List<Integer> result1 = parser.parseExpression("{1,2,3}").getValue(List.class);
System.out.println("result1中的第一个元素值是:" + result1.get(0));
// 在SpEL表达式字面量中定义的List不能被修改
try {
// List的set方法,表示将列表 result1 中的第一个元素的值设为 2
result1.set(0, 2);
} catch (Exception e) {
e.printStackTrace();
}
//对于列表中只要有一个不是字面量表达式,将返回原始List,即,不会进行不可修改处理。
String expression3 = "{1,2+4,6}";
List<Integer> result3 = parser.parseExpression(expression3).getValue(List.class);
result3.set(0, 1);
System.out.println("可以修改:" + result3);
//定义一维数组并初始化
int[] result4 = parser.parseExpression("new int[1]").getValue(int[].class);
System.out.println("声明一维数组也是可以的:" + result4[0]);
int[][] arr=new int[][]{{1,2,2},{7,8,9}};
System.out.println("java中的数组可以定义:"+ Arrays.toString(arr[0]));
try {
//但是这样不行
int[][] result5 = parser.parseExpression("new int[][]{{1,2,2},{7,8,9}}").getValue(int[][].class);
}catch (Exception e){
e.printStackTrace();
}
// 除非下面这样。
//定义二维数组并初始化
int[][] result6 = new int[][] {
{1, 2, 2},
{7, 8, 9}
};
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("result6", result6); // 将已初始化的二维数组存入 SpEL 评估上下文
int[][] resultFromSpEL = parser.parseExpression("#result6").getValue(context, int[][].class);
System.out.println("result6的第一个元素值是:" + resultFromSpEL[0][0]);
}
}
输出:
list中如果有使用变量,那么这个list也是可以修改的,不会被定义为不可修改的List对象。
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.List;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test7() {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.setVariable("number", 5);
String expression3 = "{1,#number,6}";// list中定义了变量,那么这个list也可以被修改
List<Integer> result3 = parser.parseExpression(expression3).getValue(evaluationContext,List.class);// 因为使用了变量,所以必须指定评估上下文或者根对象了。这里变量我存在评估上下文中,所以这里就给评估上下文对象。
result3.set(0, 55);
System.out.println("可以修改:" + result3);
}
}
结果:
java
可以修改:[55, 5, 6]
字典的定义
使用SpEL表达式定义字典的写法如下:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.util.Map;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testParserContext() {
ExpressionParser parser = new SpelExpressionParser();
String s="{{'key1':'value1', 'key2':'value2'}}";
Map result1 = parser.parseExpression(s).getValue(Map.class);
System.out.println(result1.get("key2"));
}
}
结果:
java
value2
当然你也可以创建一个java的Map,然后给到某个变量中去:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void testParserContext() {
ExpressionParser parser = new SpelExpressionParser();
Map<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("map1", map);
Map result1 = parser.parseExpression("#map1").getValue(context, Map.class);
System.out.println(result1.get("key2"));
}
}
结果:
java
value2
集合,字典元素访问
SpEL目前支持所有集合类型和字典类型的元素访问,即,使用"集合[索引]"访问集合元素,使用"map[key]"访问字典元素;
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.*;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test7() {
ExpressionParser parser = new SpelExpressionParser();
//SpEL内联List访问
int result1 = parser.parseExpression("{1,2,3}[0]").getValue(int.class);
System.out.println("直接拿到SpEL表达式中List的第一个元素:" + result1);
//SpEL目前支持所有集合类型的访问
Collection<Integer> collection = new HashSet<Integer>();
collection.add(1);
collection.add(3);
collection.add(2);
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable("collection", collection);
int result2 = parser.parseExpression("#collection[1]").getValue(context2, int.class);
System.out.println("拿到List集合中的第二个元素:" + result2);
//SpEL对Map字典元素访问的支持
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 197);
map.put("b", 1231);
EvaluationContext context3 = new StandardEvaluationContext();
context3.setVariable("map", map);
int result3 = parser.parseExpression("#map['a']").getValue(context3, int.class);
System.out.println("拿到map字典中的a元素:" + result3);
// 不是字符串就可以不用加引号了
Map<Integer, Integer> map2 = new HashMap<Integer, Integer>();
map2.put(10, 197);
map2.put(20, 1231);
EvaluationContext context4 = new StandardEvaluationContext();
context4.setVariable("map2", map2);
int result4 = parser.parseExpression("#map2[10]").getValue(context4, int.class);
System.out.println("拿到map2字典中的10元素:" + result4);
}
}
结果:
java
直接拿到SpEL表达式中List的第一个元素:1
拿到List集合中的第二个元素:2
拿到map字典中的a元素:197
拿到map2字典中的10元素:197
列表,字典,数组元素修改
可以使用赋值表达式或Expression接口的setValue方法修改;(注意:这里说的修改,不是修改字面量定义的list哦)
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.*;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test8() {
ExpressionParser parser = new SpelExpressionParser();
//修改list元素值(这里可不是改的字面量定义的list哦)
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
EvaluationContext context1 = new StandardEvaluationContext();
context1.setVariable("collection", list);
parser.parseExpression("#collection[0]=99").getValue(context1);
parser.parseExpression("#collection[1]").setValue(context1, 4);
System.out.println(list);
try {
// list不存在的不能添加值。只能修改已经存在的值
parser.parseExpression("#collection[2]").setValue(context1, 4);
} catch (EvaluationException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
//修改map元素值
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
map.put("b", 2);
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable("map", map);
parser.parseExpression("#map['a']").setValue(context2, 4);// 通过setValue给存在的map改变值
parser.parseExpression("#map['b']=99").getValue(context2);// 通过赋值给存在的map改变值
System.out.println(parser.parseExpression("#map['c']=100").getValue(context2));// 添加值,并且返回要赋值的值
parser.parseExpression("#map['d']").setValue(context2, 7);// 添加值
System.out.println(map);
}
}
结果:
集合投影
在SpEL中,集合投影指的是对集合中的元素进行某个处理,然后构造出另一个新的集合,该集合和原集合具有相同数量的元素。这个有点像stream流的map。投影的返回值是一个List。
SpEL使用"(list|map).![投影表达式]"来进行投影运算。
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.*;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test9() {
ExpressionParser parser = new SpelExpressionParser();
//1.测试集合或数组
List<User> list = new ArrayList<User>();
list.add(new User("张三"));
list.add(new User("李四"));
EvaluationContext context1 = new StandardEvaluationContext();
context1.setVariable("list", list);
// 不省略的写法:
Collection<String> result1 = parser.parseExpression("#list.![#this.name+',hello']").getValue(context1, Collection.class);
result1.forEach(System.out::println);
System.out.println("------------");
// 省略的写法:
List<String> result2 = parser.parseExpression("#list.![name+',hello']").getValue(context1, List.class);
result2.forEach(System.out::println);
System.out.println("=========");
//2.测试字典
Map<String, User> map = new HashMap<String, User>();
map.put("a", new User("王五"));
map.put("b", new User("赵六"));
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable("map", map);
// 取值
List<String> result3 = parser.parseExpression("#map.![#this.value.name+',你好!']").getValue(context2, List.class);
result3.forEach(System.out::println);
System.out.println("------------");
// 取键
List<String> result4 = parser.parseExpression("#map.!['键为:'+#this.key]").getValue(context2, List.class);
result4.forEach(System.out::println);
}
}
class User{
private String name;
public User(String name) {
this.name=name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
结果:
java
张三,hello
李四,hello
------------
张三,hello
李四,hello
=========
王五,你好!
赵六,你好!
------------
键为:a
键为:b
对于集合或数组使用如上表达式进行投影运算,其中投影表达式中"#this"代表每个正在遍历的集合或数组元素,可以使用比如"#this.name"来获取集合元素的属性,其中在[]中的"#this"可以省略。
Map投影的"#this"是值Map.Entry。其中可以使用"#this.value"来获取值,使用"#this.key"来获取键。其中[]中的#this.也是可以省略的。
集合选择
在SpEL中,集合选择指根据原集合通过条件表达式选择出满足条件的元素,并构造为新的集合。
SpEL使用"(list|map).?[选择表达式]",其中选择表达式结果必须是boolean类型,如果true则选择的元素将添加到新集合中,false将不添加到新集合中。
例子:
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.*;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test10() {
ExpressionParser parser = new SpelExpressionParser();
//1.测试集合或数组
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(4);
list.add(5);
list.add(7);
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("list", list);
// 中括号内要是boolean类型的才行
Collection<Integer> result = parser.parseExpression("#list.?[#this>4]").getValue(context, Collection.class);
result.forEach(System.out::println);
context.setVariable("list2", Arrays.asList(new User("张三"),new User("李四")));
Collection<User> result2 = parser.parseExpression("#list2.?[#this.name.equals('张三')]").getValue(context, Collection.class);
result2.forEach(System.out::println);
//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);
// 返回的还是Map,相当于选择了一些符合条件的键值对。
Map<String, Integer> result3 = parser.parseExpression("#map.?[key!='a']").getValue(context2, Map.class);
//3.测试投影和选择一起用
result3.forEach((key, value) -> {
System.out.println("选择后的键值对:"+key + "-" + value);
});
System.out.println("------------");
List<Integer> result4 = parser.parseExpression("#map.?[key!='a'].![value+1]").getValue(context2, List.class);
System.out.println("选择后,得到Map,然后再对Map进行投影:");
result4.forEach(System.out::println);
}
}
class User{
private String name;
public User(String name) {
this.name=name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
结果:
java
5
7
User{name='张三'}
选择后的键值对:b-2
选择后的键值对:c-3
------------
选择后,得到Map,然后再对Map进行投影:
3
4
对于集合或数组选择,如"#collection.?[#this>4]"将选择出集合元素值大于4的所有元素。选择表达式必须返回布尔类型,使用"#this"表示正在遍历的元素。集合选择的结果是集合中符合条件的元素组成的List。
对于字典选择,如"#map.?[#this.key != 'a']"将选择键值不等于"a"的,其中选择表达式中"#this"是Map.Entry类型,字典选择的结果还是字典。
同样在[]中的#this.
是可以省略的。但是如果是"#list.?[#this>4]"
,那么#this
不能省略哈。如果是"#list2.?[#this.name.equals('张三')]"
这种的话,#this.
可以省略,省略后的写法是"#list2.?[name.equals('张三')]"
,反正就是说,"#list2.?[#this.name.equals('张三')]"
和"#list2.?[name.equals('张三')]"
是一样的效果。意思自己体会。反正是这个意思。
集合选择和投影可以一起使用,如"#map.?[key != 'a'].![value+1]",会首先选择键值不等于"a"的,得到一个Map,然后在选出的Map中再进行"value+1"的投影。
在Bean定义中使用spel表达式
xml风格的配置
略。这个不讲,因为我现在用的配置文件基本都是yaml或者yml的。
注解风格的配置
@Value注解中能写的内容:
- 普通的字符串或者值。数组的话,可以@Value("1,3,2")这样写。
- 使用 来获取配置文件中的数据。这个支持给默认值,比如 @ V a l u e ( " {}来获取配置文件中的数据。这个支持给默认值,比如@Value(" 来获取配置文件中的数据。这个支持给默认值,比如@Value("{app.version:1.0.0}")这样写,如果配置文件中没有这个app.version属性,那么就会给一个默认的值"1.0.0"
- 使用#{}来写SpEL表达式,#{}内的数据会被当作SpEL表达式解析。在SpEL表达式中是支持使用@符号来获取Spring容器中的bean的,所以你在@Value中可能也会看到有写@符号的地方,其实它就是因为@Value支持SpEL。@Value在内部被解析的时候,应该是设置了默认的BeanFactoryResolver、根对象等参数的,所以可以获取到Spring中的Bean。可能就是和上面笔记《通过BeanFactory或者ConfigurableListableBeanFactory获取自动注入的bean》中的做法是原理一样的。
注意1:如果是一个大括号包括一个大括号的写法,那么内部的大括号会执行的。
注意2:#{}内不能嵌套使用#{}的。
@Value
是Spring框架中的一个注解,它主要用于从配置文件中注入属性值到Bean的字段、方法参数或构造函数中。以下是 @Value
注解的一些常见用法:
1、直接注入简单类型的值
你可以使用 @Value 注解直接将配置文件中定义的属性值注入到Bean的字段中。例如:
java
@Value("Hello World")
private String greeting;
在这个例子中,greeting 字段将被赋值为 "Hello World"。
2、注入来自配置文件的值
你可以使用占位符语法 ${...} 来引用配置文件(如 application.properties 或 application.yml)中的值。例如:
java
@Value("${app.name}")
private String appName;
在这个例子中,appName 字段将被赋值为 app.name 在配置文件中定义的值。
3、注入系统属性:
使用 #{...} 语法,你可以注入系统属性。例如:
java
@Value("#{systemProperties['os.name']}")
private String osName;
这里,osName 字段将被赋值为系统的 os.name 属性。
4、注入环境变量
同样使用 #{...} 语法,你可以注入环境变量。例如:
java
@Value("#{environment['JAVA_HOME']}")
private String myEnvVar;
这里,myEnvVar 字段将被赋值为 JAVA_HOME环境变量的值。
比如环境变量中的系统JAVA_HOME是D:\1program file\Java\jdk1.8.0_231,那么这个myEnvVar属性注入成功后它的值就是D:\1program file\Java\jdk1.8.0_231。
5、注入表达式的结果
你可以在 #{...} 中使用SpEL表达式来计算值。例如:
java
@Value("#{10 * 2}")
private int result;
这里,result 字段将被赋值为 10 * 2 的计算结果,即 20。
6、注入数组、集合和Map
你可以使用 @Value 注解来注入数组、集合和Map。例如:
java
@Value("#{'red', 'green', 'blue'}")
private List<String> colors;
@Value("#{{'key1':'value1', 'key2':'value2'}}")
private Map<String, String> configMap;
在这个例子中,colors 列表将被赋值为包含 'red', 'green', 和 'blue' 的列表,configMap 将被赋值为包含 'key1': 'value1' 和 'key2': 'value2' 的映射。
7、使用默认值
当配置文件中没有定义某个属性时,你可以为 @Value 注解提供一个默认值。例如:
java
@Value("${app.version:1.0.0}")
private String appVersion;
如果 app.version 没有在配置文件中定义,appVersion 将被赋值为默认值 "1.0.0"。
请注意,为了使 @Value 注解能够工作,你使用了@Value的这个类需要被Spring容器管理,即它应该是一个Bean(可以通过@Component、@Service、@Repository 或 @Controller等注解来标识这个类)。
此外,不能在静态变量上使用@Value,因为静态变量在类加载时初始化,而这时Spring还没有解析 @Value注解。但是可以通过给这个静态变量对应的set方法上面写@Value(......)来给静态变量绑定上你注入的值。
例子1:注入简单属性、注入静态属性(直接注入不行,但是可以通过在它的set方法上写@Value注入对应的值)、访问系统属性、初始化一个数组、初始化一个List、初始化一个Map、初始化一个随机数、访问环境变量、初始化一个计算结果、如果配置文件中没有这个变量返回默认值
java
package com.example.springbootdemo.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* @Author yimeng
* @Date 2024/6/20 21:32
* @PackageName:com.example.springbootdemo.test
* @ClassName: MyBean
* @Description: TODO
* @Version 1.0
*/
@Component
public class MyBean {
// 注入一个简单属性
@Value("Hello World")
private String simpleProperties;
// 注入静态属性
@Value("22")
private static int simpleProperties2;
// 注入静态属性
private static int simpleProperties3;
// 访问系统属性
@Value("#{systemProperties['os.name']}")
private String systemName;
// 访问环境变量
@Value("#{environment['JAVA_HOME']}")
private String myEnvVar;
// 初始化一个随机数
@Value("#{'123.3,33.5,44.6'.split(',')}")
private double myArray[];
// 初始化一个List
@Value("1,3,2")
// @Value("#{{1,3,2}}")//这样写也行,上面@Value("1,3,2")是因为@Value允许之间用逗号来隔开List的元素,下面这个是SpEL表达式的值,SpEL表达式解析完成后会把结果注入到这个myList中
private List<Integer> myList;
// 初始化一个Map
@Value("#{{'key1':1, 'key2':2}}")
private Map<String, Integer> myMap;
// 注入一个变量,值计算得到的结果
@Value("#{10 * 2}")
private int result;
// 初始化一个随机数
@Value("#{ T(java.lang.Math).random() * 100.0 }")
private double randomNumber;
// 如果app.version没有配置,则使用默认值1.0.0
@Value("${app.version:1.0.0}")
private String defaultVersionNumber;
@Value("11")
public void setSimpleProperties3(int i) {
simpleProperties3 = i;
}
public void getSimpleProperties3() {
System.out.println("注入静态属性成功: " + simpleProperties3);
}
public void getDefaultVersionNumber() {
System.out.println("默认版本号: " + defaultVersionNumber);
}
public void getResult() {
System.out.println("计算结果: " + result);
}
public void getSimpleProperties() {
System.out.println("注入简单属性: " + simpleProperties);
}
public void getSimpleProperties2() {
System.out.println("不能直接在静态属性上注入数据: " + simpleProperties2);
}
public void getMyEnvVar() {
System.out.println("访问环境变量: " + myEnvVar);
}
public void printSystemName() {
System.out.println("访问系统属性: " + systemName);
}
public void printMyArray() {
System.out.println("初始化一个数组: " + Arrays.toString(myArray));
}
public void printMyList() {
System.out.println("初始化一个List: " + myList);
}
public void printMyMap() {
System.out.println("初始化一个Map: " + myMap);
}
public void printRandomNumber() {
System.out.println("初始化一个随机数: " + randomNumber);
}
}
java
package com.example.springbootdemo;
import com.example.springbootdemo.test.MyBean;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
/**
* @Author yimeng
* @Date 2024/6/10 13:48
* @PackageName:com.example.springbootdemo
* @ClassName: SpELDemo
* @Description: TODO
* @Version 1.0
*/
@SpringBootTest
public class SpELDemo {
@Resource
private MyBean myBean;
@Test
public void test() {
myBean.getSimpleProperties();
myBean.getSimpleProperties2();
myBean.getSimpleProperties3();
myBean.printSystemName();
myBean.printMyArray();
myBean.printMyList();
myBean.printMyMap();
myBean.printRandomNumber();
myBean.getMyEnvVar();
myBean.getResult();
myBean.getDefaultVersionNumber();
}
}
结果:
例子2:使用properties文件中的属性。
application.properties:
properties
spring.application.name=SpringbootDemo
user.names=张三,李四,王五
app.maxUsers=209
# 是否启用
app.featureEnabled=false
# 角色
app.roles=ROLE_MANAGER,ROLE_USER
# 列表项
app.listItems=item1,item2,item3
app.mapItems={'key1':'value1','key2':'value2'}
实体类:
java
package com.example.springbootdemo.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
@Component
public class MyBean {
// 字符串注入
@Value("${spring.application.name}")
private String appName;
// 注入列表
@Value("${user.names}")// ${user.names}会去拿到配置文件的值,就是:"张三,李四,王五"。所以就和@Value("张三,李四,王五")是一样的效果,所以可以注入成功
private List<String> userNameList;
// 注入列表
@Value("#{'${app.listItems}'.split(',')}")// 这样就相当于是拿到配置文件中的值,然后再使用split方法进行分割变成SpEL列表。因为SpEL中的字符串是可以调用方法的,所以可以成功。
private List<String> listItems;
// 如果配置文件中没有这个变量,那么设置默认值
@Value("${app.version:1.0.0}")
private String defaultVersionNumber;
// 注入数字
@Value("${app.maxUsers:100}") // 如果配置文件中没有,就用默认值100
private int maxUsers;
// 注入布尔值
@Value("${app.featureEnabled:true}")
private boolean featureEnabled;
// 数组注入
@Value("${app.roles:ROLE_USER,ROLE_ADMIN}")
private String[] roles;
// Map注入
// @Value("#{${app.mapItems}}")// 不行。配置文件中app.mapItems的值是{'key1':'value1','key2':'value2'}。因为@Value("{'key1':'value1','key2':'value2'}")执行是识别的,所以我们不能直接获取配置文件中的数据就注入到Map中,因为这个字符串的写法是符合SpEL表达式的,所以,我们可以借助SpEL表达式来把这个值变为Map注入到这个属性里面去。并且因为内层的{}会先执行,所以在外层用#{},内层用${}可以成功。
// @Value("{'key1':'value1','key2':'value2'}")// 不行
@Value("#{${app.mapItems}}")// 可以
private Map<String, String> mapItems;
public void getMapItems() {
System.out.print("Map项: ");
for (Map.Entry<String, String> entry : mapItems.entrySet()) {
System.out.print(entry.getKey() + ": " + entry.getValue()+",");
}
System.out.println();
}
public void getListItems() {
System.out.print("列表项: ");
for (String item : listItems) {
System.out.print(item+",");
}
System.out.println();
}
public void getRoles() {
System.out.print("角色列表: ");
for (String role : roles) {
System.out.print(role+",");
}
System.out.println();
}
public void getFeatureEnabled() {
System.out.println("功能是否启用:" + featureEnabled);
}
public void getMaxUsers(){
System.out.println("最大用户数: " + maxUsers);
}
public void getAppName() {
System.out.println("应用的名字是: " + appName);
}
public void getDefaultVersionNumber() {
System.out.println("默认版本号: " + defaultVersionNumber);
}
public void getUserNameList() {
// 如果出现乱码,那么就把idea的setting的file encoding中的编码调整为utf-8就行了。
System.out.println("用户名列表: " + userNameList);
}
}
测试类:
java
package com.example.springbootdemo;
import com.example.springbootdemo.test.MyBean;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
/**
* @Author yimeng
* @Date 2024/6/10 13:48
* @PackageName:com.example.springbootdemo
* @ClassName: SpELDemo
* @Description: TODO
* @Version 1.0
*/
@SpringBootTest
public class SpELDemo {
@Resource
private MyBean myBean;
@Test
public void test() {
myBean.getAppName();
myBean.getDefaultVersionNumber();
myBean.getUserNameList();
myBean.getMaxUsers();
myBean.getFeatureEnabled();
myBean.getRoles();
myBean.getListItems();
myBean.getMapItems();
}
}
结果:
注意,如果注入的properties中有中文,可能会出现乱码,可以这样设置一下。
注意:在application.properties中也可以使用SpEL表达式的哦!
比如:
application.properties:
properties
spring.application.name=#{@bookServiceImpl.getName()}
user.names=张三,李四,王五
app.maxUsers=209
# 是否启用
app.featureEnabled=false
# 角色
app.roles=ROLE_MANAGER,ROLE_USER
# 列表项
app.listItems=item1,item2,item3
app.mapItems={'key1':'value1','key2':'value2'}
BookServiceImpl:
java
package com.example.springbootdemo.service.impl;
import com.example.springbootdemo.service.BookService;
import org.springframework.stereotype.Service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service.impl
* @ClassName: BookServiceImpl
* @Description: TODO
* @Version 1.0
*/
@Service
public class BookServiceImpl implements BookService {
@Override
public void save() {
System.out.println("book service is running ...");
}
public String getName(){
return "测试";
}
}
BookService:
java
package com.example.springbootdemo.service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service
* @ClassName: BookService
* @Description: TODO
* @Version 1.0
*/
public interface BookService {
public void save();
}
MyBean:
java
package com.example.springbootdemo.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
@Component
public class MyBean {
// 字符串注入
@Value("${spring.application.name}")// 可以,相当于是执行了@Value("#{@bookServiceImpl.getName()}")
// @Value("#{@bookServiceImpl.getName()}")// 可以
// @Value("#{${spring.application.name}}")// 不可以,
// @Value("#{#{@bookServiceImpl.getName()}}")// 不可以,因为不能双层嵌套。
private String appName;
// 注入列表
@Value("${user.names}")
private List<String> userNameList;
// 注入列表
@Value("#{'${app.listItems}'.split(',')}")
private List<String> listItems;
// 如果配置文件中没有这个变量,那么设置默认值
@Value("${app.version:1.0.0}")
private String defaultVersionNumber;
// 注入数字
@Value("${app.maxUsers:100}") // 如果配置文件中没有,就用默认值100
private int maxUsers;
// 注入布尔值
@Value("${app.featureEnabled:true}")
private boolean featureEnabled;
// 数组注入
@Value("${app.roles:ROLE_USER,ROLE_ADMIN}")
private String[] roles;
// Map注入
@Value("#{${app.mapItems}}")
private Map<String, String> mapItems;
public void getMapItems() {
System.out.print("Map项: ");
for (Map.Entry<String, String> entry : mapItems.entrySet()) {
System.out.print(entry.getKey() + ": " + entry.getValue()+",");
}
System.out.println();
}
public void getListItems() {
System.out.print("列表项: ");
for (String item : listItems) {
System.out.print(item+",");
}
System.out.println();
}
public void getRoles() {
System.out.print("角色列表: ");
for (String role : roles) {
System.out.print(role+",");
}
System.out.println();
}
public void getFeatureEnabled() {
System.out.println("功能是否启用:" + featureEnabled);
}
public void getMaxUsers(){
System.out.println("最大用户数: " + maxUsers);
}
public void getAppName() {
System.out.println("应用的名字是: " + appName);
}
public void getDefaultVersionNumber() {
System.out.println("默认版本号: " + defaultVersionNumber);
}
public void getUserNameList() {
// 如果出现乱码,那么就把idea的setting的file encoding中的编码调整为utf-8就行了。
System.out.println("用户名列表: " + userNameList);
}
}
测试类:
java
package com.example.springbootdemo;
import com.example.springbootdemo.test.MyBean;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
/**
* @Author yimeng
* @Date 2024/6/10 13:48
* @PackageName:com.example.springbootdemo
* @ClassName: SpELDemo
* @Description: TODO
* @Version 1.0
*/
@SpringBootTest
public class SpELDemo {
@Resource
private MyBean myBean;
@Test
public void test() {
myBean.getAppName();
myBean.getDefaultVersionNumber();
myBean.getUserNameList();
myBean.getMaxUsers();
myBean.getFeatureEnabled();
myBean.getRoles();
myBean.getListItems();
myBean.getMapItems();
}
}
结果(这个例子的其实就把上一个例子的application.properties配置文件中的spring.application.name=SpringbootDemo改为了spring.application.name=#{@bookServiceImpl.getName()}):
注意,不能双层嵌套,比如在appName上面写@Value("#{#{@bookServiceImpl.getName()}}")就不行。@Value("#{@bookServiceImpl.getName()}")这样写也不可以,内部的KaTeX parse error: Expected 'EOF', got '#' at position 34: ...n.name}先执行,就拿到了#̲{@bookServiceIm...{spring.application.name}")能执行,其实就是因为@Value("#{@bookServiceImpl.getName()}")能执行所以才执行成功了。上面例子里面KaTeX parse error: Expected 'EOF', got '#' at position 32: ...ion.name}拿到的值就是#̲{@bookServiceIm...{spring.application.name}")会被变为@Value("#{@bookServiceImpl.getName()}")来执行。因为@Value("#{@bookServiceImpl.getName()}")能执行成功,所以@Value("${spring.application.name}")可以执行成功。
我看拿数据的实体类里面百度上有看到部分例子加了@PropertySource("classpath:.......properties"),那么什么时候使用@PropertySource("classpath:.......properties")呢?
答:如果你要加载数据在其他配置文件中,而不是application.properties,那么你可以借助@PropertySource注解来加载那些数据。
比如:
test.properties:
properties
aaa.name=zhang
aaa.bookName=#{@bookServiceImpl.getName()}
application.properties:
properties
spring.application.name=#{@bookServiceImpl.getName()}
user.names=张三,李四,王五
app.maxUsers=209
# 是否启用
app.featureEnabled=false
# 角色
app.roles=ROLE_MANAGER,ROLE_USER
# 列表项
app.listItems=item1,item2,item3
app.mapItems={'key1':'value1','key2':'value2'}
BookService:
java
package com.example.springbootdemo.service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service
* @ClassName: BookService
* @Description: TODO
* @Version 1.0
*/
public interface BookService {
public void save();
}
BookServiceImpl:
java
package com.example.springbootdemo.service.impl;
import com.example.springbootdemo.service.BookService;
import org.springframework.stereotype.Service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service.impl
* @ClassName: BookServiceImpl
* @Description: TODO
* @Version 1.0
*/
@Service
public class BookServiceImpl implements BookService {
@Override
public void save() {
System.out.println("book service is running ...");
}
public String getName(){
return "测试";
}
}
MyBean:
java
package com.example.springbootdemo.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
@Component
@PropertySource("classpath:test.properties")
public class MyBean {
// 注入test.properties的字符串
@Value("${aaa.name}")
private String aaa;
// 注入test.properties的字符串,但是这个bookName是test.properties中注入BookServiceImpl的值
@Value("${aaa.bookName}")
private String aaaBookName;
// 字符串注入
@Value("${spring.application.name}")
private String appName;
// 注入列表
@Value("${user.names}")
private List<String> userNameList;
// 注入列表
@Value("#{'${app.listItems}'.split(',')}")
private List<String> listItems;
// 如果配置文件中没有这个变量,那么设置默认值
@Value("${app.version:1.0.0}")
private String defaultVersionNumber;
// 注入数字
@Value("${app.maxUsers:100}") // 如果配置文件中没有,就用默认值100
private int maxUsers;
// 注入布尔值
@Value("${app.featureEnabled:true}")
private boolean featureEnabled;
// 数组注入
@Value("${app.roles:ROLE_USER,ROLE_ADMIN}")
private String[] roles;
// Map注入
@Value("#{${app.mapItems}}")
private Map<String, String> mapItems;
public void getAaaBookName() {
System.out.println("aaaBookName: " + aaaBookName);
}
public void getAaa() {
System.out.println("aaa: " + aaa);
}
public void getMapItems() {
System.out.print("Map项: ");
for (Map.Entry<String, String> entry : mapItems.entrySet()) {
System.out.print(entry.getKey() + ": " + entry.getValue()+",");
}
System.out.println();
}
public void getListItems() {
System.out.print("列表项: ");
for (String item : listItems) {
System.out.print(item+",");
}
System.out.println();
}
public void getRoles() {
System.out.print("角色列表: ");
for (String role : roles) {
System.out.print(role+",");
}
System.out.println();
}
public void getFeatureEnabled() {
System.out.println("功能是否启用:" + featureEnabled);
}
public void getMaxUsers(){
System.out.println("最大用户数: " + maxUsers);
}
public void getAppName() {
System.out.println("应用的名字是: " + appName);
}
public void getDefaultVersionNumber() {
System.out.println("默认版本号: " + defaultVersionNumber);
}
public void getUserNameList() {
// 如果出现乱码,那么就把idea的setting的file encoding中的编码调整为utf-8就行了。
System.out.println("用户名列表: " + userNameList);
}
}
测试类:
java
package com.example.springbootdemo;
import com.example.springbootdemo.test.MyBean;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
/**
* @Author yimeng
* @Date 2024/6/10 13:48
* @PackageName:com.example.springbootdemo
* @ClassName: SpELDemo
* @Description: TODO
* @Version 1.0
*/
@SpringBootTest
public class SpELDemo {
@Resource
private MyBean myBean;
@Test
public void test() {
myBean.getAppName();
myBean.getDefaultVersionNumber();
myBean.getUserNameList();
myBean.getMaxUsers();
myBean.getFeatureEnabled();
myBean.getRoles();
myBean.getListItems();
myBean.getMapItems();
myBean.getAaa();
myBean.getAaaBookName();
}
}
结果:
如果不加@PropertySource("classpath:test.properties"),将会报错。
说明:
- 如果要加载application.properties之外的配置文件到spring中,必须要加@PropertySource("classpath:test.properties")才行。
- 写了@PropertySource("classpath:test.properties")会加载test.properties到spring中,但是上面例子中我们没有写@PropertySource("classpath:application.properties"),application.properties中的配置信息也一样加载进来了。说明,application.properties能自动加载,不用我们使用@PropertySource来加载。
测试一下特别的情况:例子和上面一样,唯一不同的地方就是把MyBean上的@PropertySource("classpath:test.properties")写在了BookServiceImpl上面。
现象:看到一样可以拿到test.properties中的数据。
解释:
在能被Spring扫描到的任何地方,只要有@PropertySource("classpath:test.properties"),都可以做到让这个配置文件数据给到Spring,然后@Value去就能拿到对应的属性,并且@Value还能解析拿到的SpEL表达式。
前面展示的properties的写法,下面展示yaml的写法:
BookService:
java
package com.example.springbootdemo.service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service
* @ClassName: BookService
* @Description: TODO
* @Version 1.0
*/
public interface BookService {
public void save();
}
BookServiceImpl:
java
package com.example.springbootdemo.service.impl;
import com.example.springbootdemo.service.BookService;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service.impl
* @ClassName: BookServiceImpl
* @Description: TODO
* @Version 1.0
*/
@Service
public class BookServiceImpl implements BookService {
@Override
public void save() {
System.out.println("book service is running ...");
}
public String getName(){
return "测试";
}
}
MyBean:
java
package com.example.springbootdemo.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
@Component
public class MyBean {
// 字符串注入,在配置文件中这个值是通过spel表达式获取bean拿到的
@Value("${spring.application.name}")
private String appName;
@Value("${app.name2}")
private String appName2;
// 注入test.yaml中的值
@Value("${aaa.bookName}")// 等价于下面这样写。注意:配置文件中的#{@bookServiceImpl.getName()}要加上双引号才行。这一点properties中没有要求。
// @Value("#{@bookServiceImpl.getName()}")// 一样可以成功,上面的写法等价于这种写法。只是上面的写法是随着配置文件的变化而变化的,这里是写死的罢了。
private String aaaBookName;
// 注入列表
@Value("${user.names}")
private List<String> userNameList;
// 如果配置文件中没有这个变量,那么设置默认值
@Value("${app.version:1.0.0}")
private String defaultVersionNumber;
// 注入数字
@Value("${app.maxUsers:100}") // 如果配置文件中没有,就用默认值100
private int maxUsers;
// 注入布尔值
@Value("${app.featureEnabled:true}")
private boolean featureEnabled;
// 数组注入
@Value("${app.roles:ROLE_USER,ROLE_ADMIN}")
private String[] roles;
// Map注入
@Value("#{${app.mapItems}}")
private Map<Integer, String> mapItems;
// 给静态属性赋值,必须要在set方法上面用@Value注解才行,在属性上面用会报错
// @Value("${app.staticValue}")
public static String staticValue;
@Value("${app.staticValue}")
public void setStaticValue(String staticValue) {
MyBean.staticValue = staticValue;
}
public void getMapItems() {
System.out.print("Map项: ");
for (Map.Entry<Integer, String> entry : mapItems.entrySet()) {
System.out.print(entry.getKey() + ": " + entry.getValue()+",");
}
System.out.println();
}
public void getAppName2() {
System.out.println("appName2: " + appName2);
}
public void getAaaBookName() {
System.out.println("test.yaml配置文件中的内容aaaBookName: " + aaaBookName);
}
public void getRoles() {
System.out.print("角色列表: ");
for (String role : roles) {
System.out.print(role+",");
}
System.out.println();
}
public void getFeatureEnabled() {
System.out.println("功能是否启用:" + featureEnabled);
}
public void getMaxUsers(){
System.out.println("最大用户数: " + maxUsers);
}
public void getAppName() {
System.out.println("应用的名字是: " + appName);
}
public void getDefaultVersionNumber() {
System.out.println("默认版本号: " + defaultVersionNumber);
}
public void getUserNameList() {
// 如果出现乱码,那么就把idea的setting的file encoding中的编码调整为utf-8就行了。
System.out.println("用户名列表: " + userNameList);
}
}
application.yaml:
yaml
spring:
application:
name: "#{@bookServiceImpl.getName()}" #要加上双引号才行
# 如果要引入其他的yaml配置文件得这么写。optional: 前缀表示如果导入的配置源不存在,Spring Boot 不会抛出异常,而是会忽略这个导入
# 比如,这里引入了aaaa.yaml和test.yaml配置文件,其中aaaa.yaml不存在,所以aaaa.yaml这个导入会被忽略.
config:
import:
- optional:classpath:/aaaa.yaml
- optional:classpath:/test.yaml
user:
names: 张三,李四,王五
app:
name2: 应用名
maxUsers: 209
featureEnabled: false
roles:
- ROLE_MANAGER
- ROLE_USER
mapItems: "{1:'value1',23:'value2'}"
staticValue: abc
test.yaml:
yaml
aaa:
name: zhang
bookName: "#{@bookServiceImpl.getName()}" #要加上双引号才行
测试类:
java
package com.example.springbootdemo;
import com.example.springbootdemo.test.MyBean;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
/**
* @Author yimeng
* @Date 2024/6/10 13:48
* @PackageName:com.example.springbootdemo
* @ClassName: SpELDemo
* @Description: TODO
* @Version 1.0
*/
@SpringBootTest
public class SpELDemo {
@Resource
private MyBean myBean;
@Test
public void test() {
myBean.getAppName();
myBean.getAppName2();
myBean.getAaaBookName();
myBean.getDefaultVersionNumber();
myBean.getUserNameList();
myBean.getMaxUsers();
myBean.getFeatureEnabled();
myBean.getRoles();
myBean.getMapItems();
System.out.println("静态属性的值是:"+MyBean.staticValue);
}
}
结果:
如果yaml中的中文出现乱码,使用notepad++修改一下编码就行了。然后把乱码的字符删除,重新打一下就行了。
关于@ConfigurationProperties注解。
上面说的map的注入都是利用获取到配置文件中的SpEL表达式写法的字符串,然后通过SpEL表达式转为Map的。
下面我们来看看,不用SpEL表达式,怎么把配置文件中的map属性注入到java中去。
其实就是用这个@ConfigurationProperties(prefix = "xxx")注解。
例子1:
application.yaml
yaml
spring:
application:
name: "#{@bookServiceImpl.getName()}" #要加上双引号才行
# 如果要引入其他的yaml配置文件得这么写。optional: 前缀表示如果导入的配置源不存在,Spring Boot 不会抛出异常,而是会忽略这个导入
# 比如,这里引入了aaaa.yaml和test.yaml配置文件,其中aaaa.yaml不存在,所以aaaa.yaml这个导入会被忽略.
config:
import:
- optional:classpath:/aaaa.yaml
- optional:classpath:/test.yaml
app:
name2: 应用名
mapItems: "{1:'value1',23:'value2'}"
userconfig:
name: test
# age: 27 #实体类里面有的属性,但是配置中没有这个属性,没有,用实体类中的默认值
aa: aaaa #配置有这个属性,但是实体类中没有这个属性,也不会报错
email: yimeng@qq.com
test.yaml
yaml
aaa:
name: zhang
bookName: "#{@bookServiceImpl.getName()}" #要加上双引号才行
MyBean.java
java
package com.example.springbootdemo.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class MyBean {
// 字符串注入,在配置文件中这个值是通过spel表达式获取bean拿到的
@Value("${spring.application.name}")
private String appName;
@Value("${app.name2}")
private String appName2;
// 注入test.yaml中的值
@Value("${aaa.bookName}")
private String aaaBookName;
// Map注入
@Value("#{${app.mapItems}}")
private Map<Integer, String> mapItems;
public void getMapItems() {
System.out.print("Map项: ");
for (Map.Entry<Integer, String> entry : mapItems.entrySet()) {
System.out.print(entry.getKey() + ": " + entry.getValue()+",");
}
System.out.println();
}
public void getAaaBookName() {
System.out.println("test.yaml配置文件中的内容aaaBookName: " + aaaBookName);
}
public void getAppName() {
System.out.println("应用的名字是: " + appName);
}
public void getAppName2() {
System.out.println("appName2: " + appName2);
}
}
BookService
java
package com.example.springbootdemo.service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service
* @ClassName: BookService
* @Description: TODO
* @Version 1.0
*/
public interface BookService {
public void save();
}
BookServiceImpl
java
package com.example.springbootdemo.service.impl;
import com.example.springbootdemo.service.BookService;
import org.springframework.stereotype.Service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service.impl
* @ClassName: BookServiceImpl
* @Description: TODO
* @Version 1.0
*/
@Service
public class BookServiceImpl implements BookService {
@Override
public void save() {
System.out.println("book service is running ...");
}
public String getName(){
return "测试";
}
}
MyAppProperties
java
package com.example.springbootdemo.test;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @Author yimeng
* @Date 2024/7/6 17:46
* @PackageName:com.example.springbootdemo.test
* @ClassName: Userconfig
* @Description: TODO
* @Version 1.0
*/
@ConfigurationProperties(prefix = "app.userconfig")
@Component
public class MyAppProperties {
private String name;
private int age;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "MyAppProperties{" +
"name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
'}';
}
}
测试类:
java
package com.example.springbootdemo;
import com.example.springbootdemo.test.MyAppProperties;
import com.example.springbootdemo.test.MyBean;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
/**
* @Author yimeng
* @Date 2024/6/10 13:48
* @PackageName:com.example.springbootdemo
* @ClassName: SpELDemo
* @Description: TODO
* @Version 1.0
*/
@SpringBootTest
public class SpELDemo {
@Resource
private MyBean myBean;
@Resource
private MyAppProperties myAppProperties;
@Test
public void test() {
myBean.getAppName();
myBean.getAppName2();
myBean.getAaaBookName();
myBean.getMapItems();
System.out.println(myAppProperties);
}
}
结果:
java
应用的名字是: 测试
appName2: 应用名
test.yaml配置文件中的内容aaaBookName: 测试
Map项: 1: value1,23: value2,
MyAppProperties{name='test', age=0, email='yimeng@qq.com'}
其实关键就是创建了一个实体类,专门用于接收yaml中map的。然后在那个类上面写上@ConfigurationProperties(prefix = "配置文件中map的前缀")和@Component。最后其他地方要获取这个map数据,就注入这个接收的实体类就行了,数据就在这个实体类里面。实体类的属性名对应的是配置文件中map的键名。实体类中的属性名和配置文件中这个前缀对应的map的键名要完全对上才会被赋上配置文件中的值,没有完全对上的那些属性不会被赋值,也不会报错。
好处就是我们配置文件中的这个map的格式更加舒服了而已。坏处就是,每次要接收一个map这种写法都得再加一个实体类接收才行,如果某个配置文件中的map要增加属性的话,对应的实体类也要增加属性才能接收到配置文件中的数据。
如果是properties的写法如下:
application.properties
properties
spring.application.name=#{@bookServiceImpl.getName()}
app.name2=应用名
app.mapItems={1:'value1',23:'value2'}
app.userconfig.name=test
app.userconfig.aa=aaaa
app.userconfig.email=yimeng@qq.com
test.properties
properties
aaa.name=zhang
aaa.bookName=#{@bookServiceImpl.getName()}
test.book.name=test
test.book.aa=aaaa
test.book.price=78
BookService
java
package com.example.springbootdemo.service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service
* @ClassName: BookService
* @Description: TODO
* @Version 1.0
*/
public interface BookService {
public void save();
}
BookServiceImpl
java
package com.example.springbootdemo.service.impl;
import com.example.springbootdemo.service.BookService;
import org.springframework.stereotype.Service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service.impl
* @ClassName: BookServiceImpl
* @Description: TODO
* @Version 1.0
*/
@Service
public class BookServiceImpl implements BookService {
@Override
public void save() {
System.out.println("book service is running ...");
}
public String getName(){
return "测试";
}
}
MyAppProperties
java
package com.example.springbootdemo.test;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @Author yimeng
* @Date 2024/7/6 17:46
* @PackageName:com.example.springbootdemo.test
* @ClassName: Userconfig
* @Description: TODO
* @Version 1.0
*/
@ConfigurationProperties(prefix = "app.userconfig")
@Component
public class MyAppProperties {
private String name;
private int age;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "MyAppProperties{" +
"name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
'}';
}
}
MyBean
java
package com.example.springbootdemo.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@PropertySource("classpath:test.properties")// 随便找一个能扫描到的地方写一个这个就行了
public class MyBean {
// 字符串注入,在配置文件中这个值是通过spel表达式获取bean拿到的
@Value("${spring.application.name}")
private String appName;
@Value("${app.name2}")
private String appName2;
// 注入test.yaml中的值
@Value("${aaa.bookName}")
private String aaaBookName;
// Map注入
@Value("#{${app.mapItems}}")
private Map<Integer, String> mapItems;
public void getMapItems() {
System.out.print("Map项: ");
for (Map.Entry<Integer, String> entry : mapItems.entrySet()) {
System.out.print(entry.getKey() + ": " + entry.getValue()+",");
}
System.out.println();
}
public void getAaaBookName() {
System.out.println("test.yaml配置文件中的内容aaaBookName: " + aaaBookName);
}
public void getAppName() {
System.out.println("应用的名字是: " + appName);
}
public void getAppName2() {
System.out.println("appName2: " + appName2);
}
}
TestYamlMap
java
package com.example.springbootdemo.test;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @Author yimeng
* @Date 2024/7/6 18:11
* @PackageName:com.example.springbootdemo.test
* @ClassName: TestYamlMap
* @Description: TODO
* @Version 1.0
*/
@ConfigurationProperties(prefix = "test.book")
@Component
public class TestYamlMap {
private String name;
private String aa;
private int price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAa() {
return aa;
}
public void setAa(String aa) {
this.aa = aa;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "TestYamlMap{" +
"name='" + name + '\'' +
", aa='" + aa + '\'' +
", price=" + price +
'}';
}
}
测试类:
java
package com.example.springbootdemo;
import com.example.springbootdemo.test.MyAppProperties;
import com.example.springbootdemo.test.MyBean;
import com.example.springbootdemo.test.TestYamlMap;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
/**
* @Author yimeng
* @Date 2024/6/10 13:48
* @PackageName:com.example.springbootdemo
* @ClassName: SpELDemo
* @Description: TODO
* @Version 1.0
*/
@SpringBootTest
public class SpELDemo {
@Resource
private MyBean myBean;
@Resource
private MyAppProperties myAppProperties;
@Resource
private TestYamlMap testYamlMap;
@Test
public void test() {
myBean.getAppName();
myBean.getAppName2();
myBean.getAaaBookName();
myBean.getMapItems();
System.out.println(myAppProperties);
System.out.println(testYamlMap);
}
}
结果:
java
应用的名字是: 测试
appName2: 应用名
test.yaml配置文件中的内容aaaBookName: 测试
Map项: 1: value1,23: value2,
MyAppProperties{name='test', age=0, email='yimeng@qq.com'}
TestYamlMap{name='test', aa='aaaa', price=78}
在Bean定义中SpEL的问题
想换个前缀和后缀该如何实现呢?
我们使用BeanFactoryPostProcessor接口提供postProcessBeanFactory回调方法就能解决。
BeanFactoryPostProcessor方法是在IoC容器创建好,但还未进行任何Bean初始化时,被ApplicationContext实现调用的,因此在这个阶段把SpEL前缀及后缀修改掉是安全的,具体代码如下:
java
package com.example.springbootdemo;
import com.example.springbootdemo.test.LessonModel;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
/**
* @Author yimeng
* @Date 2024/6/10 13:48
* @PackageName:com.example.springbootdemo
* @ClassName: SpELDemo
* @Description: TODO
* @Version 1.0
*/
@SpringBootTest
public class SpELDemo {
@Resource
private LessonModel lessonModel;
@Test
public void test() {
System.out.println(lessonModel.getDesc());
System.out.println(lessonModel.getBook());
}
}
java
package com.example.springbootdemo.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @Author yimeng
* @Date 2024/6/20 21:15
* @PackageName:com.example.springbootdemo.test
* @ClassName: LessonModel
* @Description: TODO
* @Version 1.0
*/
@Component
public class LessonModel {
// 访问配置类注入的bean
@Value("你好,%{@name},%{@msg},这种使用#和{}的写法就是不是模板了,比如:#{aaa}")
private String desc;
// 访问@Service等注解注入的bean
@Value("%{@bookServiceImpl.bookName()}")
private String book;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getBook() {
return book;
}
public void setBook(String book) {
this.book = book;
}
@Override
public String toString() {
return "LessonModel{" +
"desc='" + desc + '\'' +
", book='" + book + '\'' +
'}';
}
}
java
package com.example.springbootdemo.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.expression.StandardBeanExpressionResolver;
import org.springframework.stereotype.Component;
/**
* @Author yimeng
* @Date 2024/6/20 21:14
* @PackageName:com.example.springbootdemo.config
* @ClassName: SpelBeanFactoryPostProcessor
* @Description: TODO
* @Version 1.0
*/
@Component
public class SpelBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanExpressionResolver beanExpressionResolver = beanFactory.getBeanExpressionResolver();
if (beanExpressionResolver instanceof StandardBeanExpressionResolver) {
StandardBeanExpressionResolver resolver = (StandardBeanExpressionResolver) beanExpressionResolver;
resolver.setExpressionPrefix("%{");
resolver.setExpressionSuffix("}");
}
}
}
java
package com.example.springbootdemo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @Author yimeng
* @Date 2024/6/20 21:15
* @PackageName:com.example.springbootdemo.config
* @ClassName: MainConfig
* @Description: TODO
* @Version 1.0
*/
@ComponentScan
@Configuration
public class MainConfig {
@Bean
public String name() {
return "路粉";
}
@Bean
public String msg() {
return "欢迎和我一起学习java各种技术!";
}
}
java
package com.example.springbootdemo.service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service
* @ClassName: BookService
* @Description: TODO
* @Version 1.0
*/
public interface BookService {
public void save();
}
java
package com.example.springbootdemo.service.impl;
import com.example.springbootdemo.service.BookService;
import org.springframework.stereotype.Service;
/**
* @Author yimeng
* @Date 2024/4/25 9:37
* @PackageName:com.example.springbootdemo.service.impl
* @ClassName: BookServiceImpl
* @Description: TODO
* @Version 1.0
*/
@Service
public class BookServiceImpl implements BookService {
@Override
public void save() {
System.out.println("book service is running ...");
}
public String bookName(){
return "flowable原理";
}
}
结果:
这种写法和前面讲的传一个ParserContext 有什么区别?
答:这种写法可以影响到@Value中的模板前缀和后缀。但是无法影响,我们自己创建的ExpressionParser解析SpEL表达式的结果。
比如,下面这个代码就还是正常执行的。
java
package com.example.springbootdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@SpringBootTest
class SpringbootDemoApplicationTests {
@Test
public void test2() {
ExpressionParser parser = new SpelExpressionParser();
//定义模板。默认是以#{开头,以#结尾
ParserContext parserContext = new TemplateParserContext();
String s="1+2-#{3*4}/2";
// 加减乘除
String result1 = parser.parseExpression(s,parserContext).getValue(String.class);
System.out.println("result1=" + result1);
String str1 = parser.parseExpression("'Hello World!'").getValue(String.class);
int int1 = parser.parseExpression("1").getValue(Integer.class);
long long1 = parser.parseExpression("-1L").getValue(long.class);
float float1 = parser.parseExpression("1.1").getValue(Float.class);
double double1 = parser.parseExpression("1.1E+2").getValue(double.class);
int hex1 = parser.parseExpression("0xa").getValue(Integer.class);
long hex2 = parser.parseExpression("0xaL").getValue(long.class);
boolean true1 = parser.parseExpression("true").getValue(boolean.class);
boolean false1 = parser.parseExpression("false").getValue(boolean.class);
Object null1 = parser.parseExpression("null").getValue(Object.class);
System.out.println("str1=" + str1);
System.out.println("int1=" + int1);
System.out.println("long1=" + long1);
System.out.println("float1=" + float1);
System.out.println("double1=" + double1);
System.out.println("hex1=" + hex1);
System.out.println("hex2=" + hex2);
System.out.println("true1=" + true1);
System.out.println("false1=" + false1);
System.out.println("null1=" + null1);
}
}
结果(虽然我们写了那个配置文件,但是不会影响我们自己写的解析器解析SpEL表达式的结果。):
GBT的解释:
看到它说只影响了@Value注解和Spring的XML配置文件中的SpEL表达式的解析模板。
注意点:
-
运算符必须前后要用空格分开
像DIV、MOD、EQ、NE、GT、GE、LT、LE这些运算符都是一样的。
例子:
-
SpEL表达式中的关键字是不区分大小写的。
在SpEL中,标识符、变量名、属性名等都是不区分大小写的。因此,对于SpEL表达式的编写,可以使用大写、小写或混合大小写,这些都不会影响表达式的执行结果。
测试一下:
-
表达式中的字符串要加单引号。
补充内容1:Spel表达式和EL表达式的介绍与对比
这两种表达式都是为了解决 Java 开发中的灵活性和动态性问题,但在实际应用中有着不同的使用场景和功能。
EL表达式:
-
EL 表达式是 JavaEE 规范中定义的一种表达式语言,用于在 JSP、JSF 和其他 JavaEE 相关的技术中进行页面的数据绑定(让某个值可以动态变化)、表达式求值等。
-
EL 表达式使用
${}
符号来引用属性值,常用于获取对象的属性值、调用对象的方法以及进行运算。比如:
java// 定义一个对象 public class Person { private String name; private int age; // getters and setters } // 在 JSP 页面中使用 EL 表达式获取对象的属性值 <p>Name: ${person.name}, Age: ${person.age}</p>
注意:
在JSP页面中,EL(Expression Language)可以用来访问JavaBean的属性,数组元素,集合元素等,但是它不能直接调用方法。但是,在EL 2.2之后,可以使用方法引用的方式来调用方法。
在EL 2.2及以上版本中,可以使用以下语法来调用方法:
${myBean.myMethod()}
其中
myBean
是一个JavaBean对象,myMethod
是myBean
对象中的一个方法。需要注意的是,在使用EL调用方法时,调用的方法必须是公共的(public),并且必须按照JavaBeans规范编写,即采用getXXX或者isXXX的命名规范。
在早期的JSP版本中,EL不支持直接调用方法,只能用来获取属性值,但是通过标签库或自定义标签等方式,可以间接实现调用方法的功能。不过,推荐的方式是使用EL 2.2及以上版本,以便更方便地调用方法。
SpEL表达式:
-
SpEL 表达式是 Spring Framework 提供的一种表达式语言,用于在 Spring 应用中进行属性引用、条件判断、动态计算等操作。
-
SpEL 支持属性、方法调用(可以调用某个方法让方法执行)、运算符、集合操作、条件判断、正则表达式等更复杂的语法。还支持类型转换、变量引用、集合投影等高级特性。
比如:
java// 在 Spring 配置文件中使用 SpEL 表达式设置属性值 <bean id="myBean" class="com.example.MyBean"> <property name="name" value="#{person.name}" /> <property name="age" value="#{person.age + 5}" /> </bean> // 动态规则和条件判断 @Service public class OrderService { @Value("#{orderValidator.validate(#order)}") private boolean isValid; public void processOrder(Order order) { if (isValid) { // 处理有效订单 } else { // 处理无效订单 } } }
注意:
MyBatis 中既可以用#{}也可以用${}。那么mybatis中是支持EL表达式还是SpEL表达式呢?
MyBatis 中使用的是 OGNL(Object-Graph Navigation Language)表达式,而不是 EL 表达式或者 SpEL 表达式。所以既可以使用#{}也可以使用${}。
OGNL 是一种功能强大的表达式语言,它可以用于访问对象图中的属性和方法,并支持各种复杂的表达式。
在 MyBatis 中,OGNL 表达式主要用于以下场景:
- 在 XML 配置文件中,使用 OGNL 表达式来编写动态 SQL 语句。例如:
xml
<select id="getUsers" resultType="com.example.User">
SELECT * FROM users WHERE name LIKE '%${name}%'
</select>
这里的 ${name}
就是一个 OGNL 表达式,用于动态地构建 SQL 语句。
- 在 Mapper 接口中,使用 OGNL 表达式来绑定方法参数。例如:
java
public interface UserMapper {
List<User> getUsers(@Param("name") String name);
}
这里的 @Param("name")
注解就是使用 OGNL 表达式来绑定方法参数的。
- 在 Mapper 接口中,使用 OGNL 表达式来编写动态 SQL 语句。例如:
java
public interface UserMapper {
List<User> getUsers(String name, Integer age);
@Select("SELECT * FROM users WHERE name LIKE '%${name}%' AND age > #{age}")
List<User> getUsers(@Param("name") String name, @Param("age") Integer age);
}
这里的 #{age}
和 ${name}
都是 OGNL 表达式。
相关内容可以看看:为了熟练掌握动态SQL你必须要知道Mybatis中的OGNL表达式-腾讯云开发者社区-腾讯云 (tencent.com)