Spring EL表达式使用详解
什么是Spring EL表达式
- Spring EL 表达式是Spring表达式语言,支持在xml和注解中使用表达式,类似于JSP的EL,JSTL表达式语言。Spring开发中我们会经常涉及到调用各种资源的情况,包含普通文件、网址、正则表达式、系统变量、其他Bean的一些属性、配置文件、集合等等,我们就可以使用Spring的表达式语言实现资源的注入。
- 试想,我们平常通过注解或xml配置文件方式注入的Bean或Bean属性,其实都是静态注入,如果,Bean A中的一个成员变量m的值需要参考Bean B中的成员变量n的值,这种情况静态注入就显得无力。而Spring EL表达式就完全可以满足我们的种种动态的需求,甚至还能进行一些计算,功能非常强大。
- 使用Spring表达式语言,我们在项目中不需要手动管理Spring表达式的相关的接口和实例,只需要直接编写Spring表达式,Spring就会自动解析并转换表达式。
- Spring EL的格式为 #{ SpEL expression } 。Spring表达式主要写在注解 @Value的参数中,它的作用是通过spring把值注入给某个属性。
下面以注解的方式列举一些Spring表达式的常用用法。xml配置文件中也是同样的用法。
注入字面值
表达式支持各种类型的字面值。字符串或字符类型的字面值需要使用单引号包括,其他类型字面值直接写就行。示例如下:
Computer
@Data
@Component
public class Computer {
@Value("#{2}")
private Integer id;
@Value("#{'黑色'}")
private String color;
@Value("#{'联想'}")
private String brand;
}
User
@Data
@Component
public class User {
//注入整数类型
@Value("#{18}")
private Integer age;
//注入浮点数类型
@Value("#{58.5}")
private Double weight;
//注入布尔数类型
@Value("#{true}")
private Boolean isGirl;
//注入字符类型
@Value("#{'f'}")
private Character gender;
//注入字符串类型
@Value("#{'lucy'}")
private String username;
//注入id为computer的bean
@Value("#{computer}")
private Computer computer;
}
从容器中获取user对象并打印,结果如下:
八月 01, 2019 10:06:58 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7daf6ecc: startup date [Thu Aug 01 10:06:58 CST 2019]; root of context hierarchy
八月 01, 2019 10:06:58 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
User(age=18, weight=58.5, isGirl=true, gender=f, username=lucy, computer=Computer(id=2, color=黑色, brand=联想))
Process finished with exit code 0
可以看到所有的信息都被正常注入。
注入操作系统(OS)的属性
Spring EL表达式还可以获取操作系统的属性,我们可以注入到需要的变量中,示例如下:
User
@Data
@Component
public class User {
//注入操作系统的属性
@Value("#{systemProperties['os.name']}")
private String OSName;
//注入操作系统的属性
@Value("#{systemProperties['file.encoding']}")
private String fileEncoding;
}
从容器中获取对象信息,并和手动获取的操作系统属性进行对比
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println(applicationContext.getBean("user"));
System.out.println("========手动获取信息=======");
Properties properties = System.getProperties();
System.out.println("os.name:" + properties.getProperty("os.name"));
System.out.println("file.encoding:" + properties.getProperty("file.encoding"));
运行结果如下:
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
User(OSName=Windows 10, fileEncoding=UTF-8)
========手动获取信息=======
os.name:Windows 10
file.encoding:UTF-8
Process finished with exit code 0
注入properties配置文件中数据
我们可以在需要的时候取出properties配置文件中的数据,注入到bean变量。我们知道spring配置文件中可以通过util:properties和context:property-placeholder 两种标签来加载properties配置文件。不同加载方式,我们在Spring EL表达式中获取值的方式也不一样,区别如下:
- util:properties
它是以声明bean方式来使用,创建了一个bean,这样我们在下面使用Spring EL取值的时候格式为 #{id[‘key’]} 获取bean的属性。id为util:properties标签中id属性,key为配置文件中的key。 - context:property-placeholder
它是将配置文件加载至spring上下文中,我们只能通过${key}取得值,常用于bean的属性上,key为配置文件中的key。
下面通过一个简单的示例,来取出两种加载方式加载的properties配置文件的值。
db1.properties
# db1.properties
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://127.0.0.1:3306/java
db2.properties
# db2.properties
mysql.username=root
mysql.password=abc
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--使用context:property-placeholder加载类路径下的db1.properties-->
<context:property-placeholder location="classpath:db1.properties"/>
<!--使用util:properties加载类路径下的db2.properties配置文件,id属性将在Spring EL中使用-->
<util:properties id="db" location="classpath:db2.properties"/>
<!--指定自动扫描的包路径-->
<context:component-scan base-package="com.ls.entity"/>
</beans>
User
@Data
@Component
public class User {
/*注入db1.properties中数据,db1.properties是xml配置文件中使用context:property-placeholder标签加载的
mysql.username为db1.properties中的key
*/
@Value("${mysql.driver}")
private String driver;
/*注入db2.properties中数据,db2.properties是xml配置文件中util:properties标签来加载的
db为util:properties标签声明的id属性值
mysql.driver为db2.properties中的key
*/
@Value("#{db['mysql.username']}")
private String username;
}
获取user对象并打印,结果如下:
八月 01, 2019 11:15:44 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7daf6ecc: startup date [Thu Aug 01 11:15:44 CST 2019]; root of context hierarchy
八月 01, 2019 11:15:44 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
八月 01, 2019 11:15:45 上午 org.springframework.context.support.PropertySourcesPlaceholderConfigurer loadProperties
信息: Loading properties file from class path resource [db1.properties]
八月 01, 2019 11:15:45 上午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
User(driver=com.mysql.jdbc.Driver, username=root)
Process finished with exit code 0
可以看到,通过两种方式加载的properties配置文件我们可以对应通过两种不同的写法获取到配置文件中的值,并成功的注入到user的成员变量。
Bean属性调用
我们还可以通过Spring EL表达式,调用容器中已有其他bean的属性。只要使用点号引用属性即可。属性可直接使用属性名,属性名首字母大小写均可(只有首字母可不区分大小写);如,将容器中id为computer的对象的brand属性取出,赋值为user的likeBrand变量,我们可以这样写。
Computer
@Data
@Component
public class Computer {
@Value("#{'联想'}")
private String brand;
}
User
@Data
@Component
public class User {
//将容器中id为computer的对象的brand属性取出,赋值为user的likeBrand
@Value("#{computer.brand}")
private String likeBrand;
}
获取对象并打印结果如下:
信息: Loading properties file from class path resource [db2.properties]
User(likeBrand=联想)
Process finished with exit code 0
Bean方法调用
我们还可以通过Spring EL表达式,调用容器中已有其他bean的成员或字符串的方法。Spring 会将方法返回值注入到属性中。只要使用点号引用方法名即可。方法使用方式和Java语法一样。
Computer
@Data
@Component
public class Computer {
public String getStr() {
return "Hello";
}
public String getStr(String string, int i) {
return "Hello " + string + i;
}
}
User
@Data
@Component
public class User {
//调用id为computer的bean的无参方法getStr,返回值注入str1
@Value("#{computer.getStr()}")
private String str1;
//调用id为computer的bean的有参方法getStr,参数可以直接传递
@Value("#{computer.getStr('World',666)}")
private String str2;
//如果希望方法返回值的小写形式注入,可以直接继续调用方法
@Value("#{computer.getStr('World',666).toLowerCase()}")
private String str3;
//也可以直接调用字符串的某个方法,将返回值注入,如将字符串的所有空格替换成空字符串的结果注入
@Value("#{'h e l l o w o r l d'.replaceAll(' ','')}")
private String str4;
}
获取user对象并打印,结果如下:
信息: Loading properties file from class path resource [db2.properties]
User(str1=Hello, str2=Hello World666, str3=hello world666, str4=helloworld)
Process finished with exit code 0
T运算符
T操作符可以获取表达式对象的类型, 可以调用表达式对象的静态方法
Computer
@Data
@Component
public class Computer {
public static String getStr() {
return "Hello ";
}
public static String getStr(String string, double price) {
return "Hello " + string + ",电脑价格为:" + price;
}
}
User
@Data
@Component
public class User {
//获取表达式对象的类型
@Value("#{T(java.lang.String)}")
private Class clazz1;
//如果类型是java.lang包下的可以省略包名
@Value("#{T(String)}")
private Class clazz2;
//如果类型不是java.lang包下的,一定不能省略,否则报错
@Value("#{T(java.util.Scanner)}")
private Class clazz3;
@Value("#{T(com.ls.entity.Computer)}")
private Class clazz4;
//调用静态方法
@Value("#{T(com.ls.entity.Computer).getStr()}")
private String str;
//调用静态方法
@Value("#{T(com.ls.entity.Computer).getStr('World',55.5)}")
private String str2;
}
获取user对象并打印,结果如下:
信息: Loading properties file from class path resource [db2.properties]
User(clazz1=class java.lang.String, clazz2=class java.lang.String, clazz3=class java.util.Scanner, clazz4=class com.ls.entity.Computer, str=Hello , str2=Hello World,电脑价格为:55.5)
Process finished with exit code 0
构造器
在Spring EL表达式中,也使用new关键字来调用构造器,如果new的是java.lang包下的类的对象,可以省略包名。如果是自定义的类或者非java.lang包下的类,类名需要写全限定名。
User
@Data
@Component
public class User {
//调用Computer的两个参数的构造方法,为User的Computer属性注入Computer对象
@Value("#{new com.ls.entity.Computer('白色',66)}")
private Computer computer;
//注入新new的StringBuffer对象
@Value("#{new StringBuffer('hello world!')}")
private StringBuffer stringBuffer;
}
获取user对象并打印,结果如下:
信息: Loading properties file from class path resource [db2.properties]
User(computer=Computer(color=白色, price=66), stringBuffer=hello world!)
Process finished with exit code 0
内联集合给集合赋值
我们可以直接在表达式中定义集合,直接给bean对象的集合变量赋值。这就是内联。使用花括号语法。
Computer
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Computer {
private String color;
private Double price;
}
User
@Data
@Component
public class User {
// 给Integer数组赋值
@Value("#{{1,2,3,4}}")
private Integer[] ids;
// 给String数组赋值,使用单引号,单引号里面可以写字符串
@Value("#{{'a','b','c'}}")
private String[] hobbies;
// 给char数组赋值,使用单引号,单引号里面只能写字符
@Value("#{{'a','b','c'}}")
private char[] chars;
//给List集合赋值
@Value("#{{1,2,3,4}}")
private List<Integer> ids2;
//给List集合赋值
@Value("#{{'aa','b','c'}}")
private List<String> hobbies2;
//给List集合赋bean类型的值,不会自动去掉重复的
@Value("#{{new com.ls.entity.Computer('白色',55.5),new com.ls.entity.Computer('黑色',66),new com.ls.entity.Computer('白色',55.5)}}")
private List<Computer> computers;
//给Set集合赋值,会自动去掉重复的
@Value("#{{1,2,1,4}}")
private Set<Integer> ids3;
//给Set集合赋值,会自动去掉重复的
@Value("#{{'aa','b','aa'}}")
private Set<String> hobbies3;
//给Set集合赋bean类型的值,因为Computer重写了hashCode和equals方法,set集合会自动去掉重复的Computer
@Value("#{{new com.ls.entity.Computer('白色',55.5),new com.ls.entity.Computer('黑色',66),new com.ls.entity.Computer('白色',55.5)}}")
private Set<Computer> computers2;
//给二维数组赋值
@Value("#{{{'r1c1','r1c2'},{'r2c1','r2c2','r2c3'}}}")
private String[][] address;
//给嵌套集合赋值
@Value("#{{{'aa','bb'},{'xx','yy','zz'}}}")
private List<List<String>> address2;
}
获取user对象并打印,结果如下:
八月 01, 2019 3:10:05 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7daf6ecc: startup date [Thu Aug 01 15:10:05 CST 2019]; root of context hierarchy
八月 01, 2019 3:10:06 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
八月 01, 2019 3:10:06 下午 org.springframework.context.support.PropertySourcesPlaceholderConfigurer loadProperties
信息: Loading properties file from class path resource [db1.properties]
八月 01, 2019 3:10:06 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
User(ids=[1, 2, 3, 4], hobbies=[a, b, c], chars=[a, b, c], ids2=[1, 2, 3, 4], hobbies2=[aa, b, c], computers=[Computer(color=白色, price=55.5), Computer(color=黑色, price=66.0), Computer(color=白色, price=55.5)], ids3=[1, 2, 4], hobbies3=[aa, b], computers2=[Computer(color=白色, price=55.5), Computer(color=黑色, price=66.0)], address=[[r1c1, r1c2], [r2c1, r2c2, r2c3]], address2=[[aa, bb], [xx, yy, zz]])
Process finished with exit code 0
内联Map给map赋值
使用类似JSON的语法,键和值之间用冒号隔开。
User
@Data
@Component
public class User {
//给map集合赋值:格式#{{key:value,key2:value2}}
@Value("#{{name:'lucy',age:18}}")
private Map<String, String> map;
//给map集合赋值:格式#{{key:value,key2:value2}}
@Value("#{{person:{name:'lucy',age:18,address:'北京'}}}")
private Map<String, Map<String, String>> map2;
}
获取user对象并打印,结果如下:
信息: Loading properties file from class path resource [db2.properties]
User(map={name=lucy, age=18}, map2={person={name=lucy, age=18, address=北京}})
Process finished with exit code 0
给数组赋值
上面我们看到,我们可以使用内联的方式给数组赋值。我们还可以使用类似Java的语法给数组赋值,可以给出初始值,多维数组也受支持。
User
@Data
@Component
public class User {
//直接new一个数组
@Value("#{new int[5]}")
private int[] ids;
//new数组的同时还可以,指定初始值
@Value("#{new int[3]{1,2,3}}")
private int[] ids2;
//还可以给多维数组初始化,二维数组使用new初始化的时候,不可以给初始值了,否则报错
@Value("#{new int[3][2]}")
private int[][] ids3;
}
获取user对象并打印,结果如下:
八月 01, 2019 3:38:23 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
User(ids=[0, 0, 0, 0, 0], ids2=[1, 2, 3], ids3=[[0, 0], [0, 0], [0, 0]])
Process finished with exit code 0
Elvis运算符
Spring EL表达式支持Elvis运算符,语法是变量?:默认值
意思是当某变量不为 null 或不表达空的意思(如空字符串)的时候使用该变量,当该变量为 null 或表达空的意思的时候使用指定的默认值。
Computer
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Computer {
//string1赋值白色字符串
@Value("#{'白色'}")
private String string1;
//string2赋值空字符串
@Value("#{''}")
private String string2;
//string3不赋值,默认为null
private String string3;
}
User
@Data
@Component
public class User {
//如果Spring容器中id为computer对象的string1属性为空字符串或null则赋值为默认值哈哈,否则赋值为computer对象的string1属性
@Value("#{computer.string1?:'哈哈'}")
private String username1;
@Value("#{computer.string2?:'哈哈2'}")
private String username2;
@Value("#{computer.string3?:'哈哈3'}")
private String username3;
}
获取computer和user对象并打印,结果如下:
八月 01, 2019 3:47:52 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
Computer(string1=白色, string2=, string3=null)
User(username1=白色, username2=哈哈2, username3=哈哈3)
Process finished with exit code 0
#this和#root
#this和#root代表了表达式上下文的对象,我们可以提前定义一个上下文根对象,这样就可以使用#root来引用这个根对象。而且#root被定义始终引用上下文根对象。通过跟队形可以向表达式公开一个全面的自定义量。#this则根据当前求值环境的不同而变化。#this来表示当前的对象. 常用于集合的过滤,下面的例子中,#this即每次循环的值。
Computer
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Computer {
private String brand;
@Value("#{{1,2,3,4,5}}")
private List<Integer> computerIds;
}
User
@Data
@Component
public class User {
//#root用法演示
static {
//手动创建解析器来解析表达式
//造一个电脑对象,用于将其存入上下文对象的根对象进行演示
Computer computer = new Computer();
computer.setBrand("华硕");
//获取上下文对象
StandardEvaluationContext context = new StandardEvaluationContext();
//将computer存入上下文根对象,以在别的地方使用Spring EL表达式#root来获取在此存入的computer对象
context.setRootObject(computer);
//手动创建一个解析器
ExpressionParser parser = new SpelExpressionParser();
//Spring EL表达式,取出上下文根对象,由于根对象为我们手动存的电脑,所以可以获取其brand属性值
String statement = "#root.brand";
//解析表达式
Expression expression = parser.parseExpression(statement);
//获取解析后的结果
String result = expression.getValue(context, String.class);
//打印结果
System.out.println("取出根对象computer的brand属性为:"+result);
}
//#this用法演示
/*
集合.?[expression]: 是一种语法,本文后面即有介绍,目的是选择符合条件的元素
#this即代表遍历集合时每次循环的值,此处意思是遍历容器中id为computer对象的
computerIds属性(前提是此属性是一个集合),选出集合中大于2的所有元素生成
一个新的集合,并将新的结合注入给user对象的userIds属性.
*/
@Value("#{computer.computerIds.?[#this>2]}")
private List<Integer> userIds;
}
获取computer和user对象并打印,结果如下:
八月 01, 2019 4:38:13 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
取出根对象computer的brand属性为:华硕
Computer(brand=null, computerIds=[1, 2, 3, 4, 5])
User(userIds=[3, 4, 5])
Process finished with exit code 0
操作符(运算符)
表达式中支持各种运算符,运算规则和Java规则类似。常用运算符如下:
- 数学运算符
加 (+),减 (-),乘 (*),除 (/),取模 (%),取幂(^) - 关系运算符
等于 (==, eq),不等于 (!=, ne),小于 (<, lt),小于等于(<= , le),大于(>, gt),大于等于 (>=, ge) (注意:Xml配置,应该用“<"代替小于号“<”,用“le”代替小于等于“<=” ) - 逻辑运算符
and(&&),or(||),and not(!) - 其他运算符
三元操作符, instanceof, 正则匹配,between等
下面演示Spring EL操作符
Computer
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Computer {
//注入字符串str1
@Value("#{'str1'}")
private String str1;
//注入空字符串
@Value("#{''}")
private String str2;
//不赋值默认为 null
private String str3;
}
User
@Data
@Component
public class User {
//数学运算符
//加法
@Value("#{3 + 2}")
private int jia; //5
//减法
@Value("#{3 - 2}")
private int jian; //1
//乘法
@Value("#{3 * 2}")
private int cheng; //6
//除法
@Value("#{3 / 2}")
private int chu; //1
//取模
@Value("#{3 % 2}")
private int mo; //1
//取幂
@Value("#{3 ^ 2}")
private int mi; //9
//逻辑运算符,注意&&和||必须使用两个&或|才行,不能只使用一个
//and
@Value("#{true and false && false}")
private boolean booleanAnd; //false
//or
@Value("#{true or false || false}")
private boolean booleanOr; //true
//not
@Value("#{not true}")
private boolean booleanNot; //false
@Value("#{! true}")
private boolean booleanNot2;//false
//关系运算符
//等于
@Value("#{1 == 1 or 1 eq 1}")
private boolean dengYu; //true
//不等于
@Value("#{1 != 1 or 1 ne 1}")
private boolean buDengYu; //false
//小于
@Value("#{1 < 1 or 1 lt 1}")
private boolean xiaoYu; //false
//小于等于
@Value("#{1 <= 1 or 1 le 1}")
private boolean xiaoYuDengYu;//true
//大于
@Value("#{1 > 1 or 1 gt 1}")
private boolean daYu; //false
//大于等于
@Value("#{1 >= 1 or 1 ge 1}")
private boolean daYuDengYu; //true
//非空值val,那么表达式val > null恒为真
@Value("#{'' > computer.str1}")
private boolean valAndNull; //false
@Value("#{'' >= computer.str2}")
private boolean valAndNull2; //true
@Value("#{'' > computer.str3}")
private boolean valAndNull3; //true
//其他运算符
//三元操作符/三目运算符
@Value("#{3 > 2 ? 5 : 6}")
private int age; //5
//instanceof使用
@Value("#{1 instanceof T(Integer)}")
private boolean instanceofTest; //true
@Value("#{computer instanceof T(com.ls.entity.Computer)}")
private boolean instanceofTest2; //true
/*
between使用,判断一个数据是否在两个数据之间
格式:a between {m,n}
m的值必须在n前面,也就是m必须比n小
m和n不能交换顺序,否则虽然不会报错但是无法获得正确的比较结果
*/
@Value("#{3 between {2,5}}")
private boolean betweenTest; //true
@Value("#{'ab' between {'aa','ac'}}")
private boolean betweenTest2; //true
//正则表达式匹配,我们可以使用正则表达式和 matches关键字匹配数据,只有完全匹配的时候返回true,match前的字符串不能为null否则报错
@Value("#{'35' matches '\\d+'}")
private boolean regExp; //true
@Value("#{computer.str1 matches '\\w+'}")
private boolean regExp2; //true
}
获取computer和user对象并打印,结果如下:
八月 01, 2019 5:38:19 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
Computer(str1=str1, str2=, str3=null)
User(jia=5, jian=1, cheng=6, chu=1, mo=1, mi=9, booleanAnd=false, booleanOr=true, booleanNot=false, booleanNot2=false, dengYu=true, buDengYu=false, xiaoYu=false, xiaoYuDengYu=true, daYu=false, daYuDengYu=true, valAndNull=false, valAndNull2=true, valAndNull3=true, age=5, instanceofTest=true, instanceofTest2=true, betweenTest=true, betweenTest2=true, regExp=true, regExp2=true)
Process finished with exit code 0
注意:空值的处理,假设有非空值val,那么表达式 val > null 恒为真,这一点需要注意。
安全导航运算符
这是来自Groovy的一个功能,语法是?.
,当然有些语言也提供了这个功能。当我们对对象的某个属性求值时,如果该对象本身为空,就会抛出空指针异常,如果使用安全导航运算符,空对象的属性就会简单的返回空。
Computer
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Computer {
//注入字符串str1
@Value("#{'str1'}")
private String str1;
//注入空字符串
@Value("#{''}")
private String str2;
//不赋值默认为 null
private String str3;
}
User
@Data
@Component
public class User {
//安全导航运算符 ?. 避免空指针异常,如果调用对象为null则直接返回null,避免了空指针异常
@Value("#{computer.str1?.concat('abc')}")
private String str1;
@Value("#{computer.str2?.concat('abc')}")
private String str2;
@Value("#{computer.str3?.concat('abc')}")
private String str3;
}
获取computer和user对象并打印,结果如下:
八月 01, 2019 5:48:49 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
Computer(str1=str1, str2=, str3=null)
User(str1=str1abc, str2=abc, str3=null)
Process finished with exit code 0
我们看到computer.str3为null并没有报空指针异常,而是直接给str3注入了null,另外两项不为null的都正常执行,后面拼接了字符串abc。
从数组、List、Map集合取值
我们可以使用Spring EL表达式从数组,list集合,set集合或map中取值。
- 数组和列表可以使用方括号引用对应索引的元素,list[index]。
- Map类型可以使用方括号引用键对应的值,map[key]。
Computer
@Data
@Component
public class Computer {
//给数组赋值
@Value("#{{1,2,3,4}}")
private Integer[] ids;
//给List集合赋值
@Value("#{{'list1','list2','list3'}}")
private List<String> hobbiesList;
//给List集合赋值
@Value("#{{'set1','set3','set2'}}")
private Set<String> hobbiesSet;
@Value("#{{'key1':'value1','key2':'value2'}}")
private Map<String,String> map;
}
User
@Data
@Component
public class User {
//取出数组0号索引的值
@Value("#{computer.ids[0]}")
private Integer id;
//取出List集合中索引为0的值
@Value("#{computer.hobbiesList[0]}")
private String hobbyList;
//取出Set集合中索引为0的值,set集合也可以通过索引取值,没想到吧
@Value("#{computer.hobbiesSet[0]}")
private String hobbySet;
//取出map集合中key为key1的值
@Value("#{computer.map['key1']}")
private String MapStr;
}
获取computer和user对象并打印,结果如下:
八月 01, 2019 6:08:30 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
Computer(ids=[1, 2, 3, 4], hobbiesList=[list1, list2, list3], hobbiesSet=[set1, set3, set2], map={key1=value1, key2=value2})
User(id=1, hobbyList=list1, hobbySet=set1, MapStr=value1)
Process finished with exit code 0
注意:此种方式甚至可以从set集合中根据索引取值。
集合选择
我们可以在Spring EL表达式中对集合进行过滤选择。Spring会迭代集合对象的每一个元素,并使用选择表达式判断该元素是否满足条件,最后返回由满足条件的元素组成的新的集合。共有如下四种用法:
- ? [expression]: 选择符合条件的元素
- ^ [expression]: 选择符合条件的第一个元素
- $ [expression]: 选择符合条件的最后一个元素
- ! [expression]: 可对集合中的元素挨个进行处理
对于集合可以使用 #this 变量代表每次迭代出的元素进行过滤, 对于map, 可分别对keySet及valueSet分别使用key和value关键字,分别代表每次迭代出的键和值;
Computer
@Data
@Component
public class Computer {
//给List集合赋值
@Value("#{{'list1','list2','list3','list4'}}")
private List<String> list;
//给map集合赋值
@Value("#{{'key1':'value1','key2':'value2','key3':'value3','key4':'value4'}}")
private Map<String, String> map;
}
User
@Data
@Component
public class User {
//筛选集合中所有大于list2的数据组成新集合并返回
@Value("#{computer.list.?[#this > 'list2']}")
private List<String> list1;
//筛选集合中第一个大于list2的数据组成新集合并返回
@Value("#{computer.list.^[#this > 'list2']}")
// private List<String> list2;
private String Str2;
//筛选集合中最后一个大于list2的数据组成新集合并返回
@Value("#{computer.list.$[#this > 'list2']}")
private List<String> list3;
//集合中所有元素加字符串abc
@Value("#{computer.list.![#this + 'abc']}")
private List<String> list4;
//筛选map中所有key大于key2的数据组成新map并返回
@Value("#{computer.map.?[key > 'key2']}")
private Map<String, String> map1;
//筛选map中第一个key大于key2的数据组成新map并返回
@Value("#{computer.map.^[key > 'key2']}")
private Map<String, String> map2;
//筛选map中最后一个value大于value2的数据组成新map并返回
@Value("#{computer.map.$[value > 'value2']}")
private Map<String, String> map3;
//筛选map中最后一个key小于key5并且value大于value2的数据组成新map并返回
@Value("#{computer.map.$[key < 'key5' and value > 'value2']}")
private Map<String, String> map4;
//map中所有key加字符串abc,返回值为List集合而不再是map集合
@Value("#{computer.map.![key + 'abc']}")
private List<String> mapToList;
//map中所有value加字符串abc,返回值为List集合而不再是map集合
@Value("#{computer.map.![value + 'abc']}")
private List<String> mapToList2;
}
获取computer和user对象并打印,结果如下:
八月 01, 2019 6:47:18 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
Computer(list=[list1, list2, list3, list4], map={key1=value1, key2=value2, key3=value3, key4=value4})
User(list1=[list3, list4], Str2=list3, list3=[list4], list4=[list1abc, list2abc, list3abc, list4abc], map1={key3=value3, key4=value4}, map2={key3=value3}, map3={key4=value4}, map4={key4=value4}, mapToList=[key1abc, key2abc, key3abc, key4abc], mapToList2=[value1abc, value2abc, value3abc, value4abc])
Process finished with exit code 0
注意:通过^ [expression] 和 $ [expression] 筛选出来的结果一定最多只有一个,因此可以给非集合类型注入,如:如果筛选出来的是字符串则可以给String字符串类型注入。但是 ? [expression]或 ! [expression] 返回的符合条件的结果哪怕实际真的只有一个也不能注入非集合类型。
集合投影
我们可以在Spring EL表达式中,将一个集合中所有元素的某属性抽取出来,组成一个新集合。语法是![投影表达式]。(其实在上面我们已经使用过了。。。-_-)
Computer
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Computer {
private int price;
private String brand;
}
User
@Data
@Component
public class User {
//new 三个Computer对象给集合赋值
@Value("#{{new com.ls.entity.Computer(88,'联想'),new com.ls.entity.Computer(77,'弘基'),new com.ls.entity.Computer(99,'华硕')}}")
private List<Computer> computers;
//将computers集合中所有computer的brand属性投影到brands集合中,组成新集合
@Value("#{user.computers.![#this.brand]}")
private List<String> brands;
//将computers集合中所有computer的price属性值乘以10投影到 prices集合中,组成新集合
@Value("#{user.computers.![#this.price * 10]}")
private List<String> prices;
}
获取computer和user对象并打印,结果如下:
八月 01, 2019 7:00:05 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
Computer(price=0, brand=null)
User(computers=[Computer(price=88, brand=联想), Computer(price=77, brand=弘基), Computer(price=99, brand=华硕)], brands=[联想, 弘基, 华硕], prices=[880, 770, 990])
Process finished with exit code 0
${}和#{}的区别
首先,${}是变量用来插值的一种表达式。#{}是SPEL表达式。他们的作用都是可以通过spring把值注入给某个属性。
- ${key名称}:
- 用户获取外部文件中指定key的值
- 可以出现在xml配置文件中,也可以出现在注解@Value中
- 一般用户获取数据库配置文件的内容信息等
- #{表达式}:
- 是Spring EL表达式的格式
- 可以出现在xml配置文件中,也可以出现在注解@Value中
- 可以任意表达式,支持运算符等
在使用的时候也允许 #{'$(key)'}
这样的格式使用.比如:@Value("#{’ ${jdbc.url}’}");