我们通常讨论依赖注入的时候,讲的都是将一个Bean引入到另一个Bean的属性或构造器参数中,它通常指的是将一个对象与另一个对象进行关联。而装配Bean的另一个方面指的是将一个值注入到Bean的属性或构造器中,也就是字面量的注入。
比如,将专辑的名字装配到TaylorSwift Bean的构造器或title属性中:
@Bean
public CompactDisc taylorSwift() {
return new TaylorSwift("titledemo","artistdemo");
}
这实现了我们为BlankDisc Bean设置title和artist的需求,但它在实现的时候是将值硬编码在配置类中的,同理XML也是。但有时候我们可能会希望避免硬编码值,而是想让这些值在运行时再确定。
为了实现,Spring提供了两种在运行时求值的方式:
- 属性占位符
- Spring表达式语言(spEL)
使用属性占位符注入外部的值
处理外部值的最简单方式就是声明属性源并通过Spring的Environment来检索属性。
3.21:使用PropertySource注解和Environment
@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig {
@Autowired
Environment env;
@Bean
public TaylorSwift disc() {
return new TaylorSwift(
env.getProperty("disc.title"),
env.getProperty("disc.artist"));
}
}
我们看到上面的代码中@PropertySource引用了app.properties的文件,文件内容可能如下:
3.22:app.properties
disc.title = titledemo;
disc.artist = artistdeomo;
这个文件会加载到Spring的Environment中,稍后会从这个检索属性。同时disc()方法中,会创建一个新的BlankDisc,它的构造器参数是从属性文件中获取的,而这是通过getProperty()实现的。
其实getProperty()不是获取属性值的唯一方法,我们下来就深入学习Spring的Environment。
深入学习Spring的Environment
getProperty()有四个重载的变种形式:
- String getProperty(String key)
- String getProperty(String key, String defaultValue)
- T getProperty(String key, Class< T> type)
- T getProperty(String key, Class< T> type, T defaultValues)
1)第一种就是我们上面代码的例子
2)第二种是针对如果指定属性不存在的时候,我们也可以这样写,使用一个默认值:
env.getProperty("disc.title","aaa");
env.getProperty("disc.artist","bbb");
但是当属性不存在并且也没有指定默认值的情况下,将会抛出IllegalStateException异常。
检查属性是否存在可以调用Environment的containsProperty()方法:
boolean titleExists = env.containsProperty("disc.title");
3)后两种与前两种类似,但是不会将所有的值都认为String类。例如下面的代码,当我们想使用Integer,但从属性文件中得到了String类型,就会这样做:
int connectionCount = env.getProperty("db.connection.count", Integer.class, 30);
Environment还提供了一些其他的方法:
- String getRequiredProperty(String key):希望这个属性必须定义,当属性未定义并且也没有指定默认值的情况下,将会抛出IllegalStateException异常
- Class< T> getPropertyAsClass(String key, …class):将属性解析为类
- String[] getActiveProfiles():返回激活profile名称的数组
- String[] getDefaultProfiles():返回默认profile名称的数组
- boolean acceptsProfiles(String… profiles):如果Environment支持给定profile的话,返回true
直接从Environment中检索属性的非常方便的,但Spring也提供了通过占位符装配属性的方法。
解析属性占位符
在Spring装配中,占位符的形式为使用“${…}”包装的属性名称。
比如,在XML中解析TaylorSwift构造器参数:
3.23:在XML中使用占位符解析构造器参数
<bean id = "idemo"
class = "soundsystem.TaylorSwift"
c:_title = "${disc.title}"
c:_artist = "${disc.artist}" />
可以看到title和artist构造器参数的值时从一个属性中解析到的,这个属性的名称为disc.title和disc.artist。在XML文件中,并没有使用任何硬编码的值,它的值时从XML配置文件以外的一个源中解析得到的。
如果我们依赖与组件扫描和自动装配来创建和初始化应用组件的话,那么就没有指定占位符的配置文件或类了,在这种情况下,我们可以在Java代码中使用@Value注解来使用占位符,比如在TaylorSwift的类中,它的构造器可以这样写:
3.24:在Java代码中使用占位符
public TaylorSwift(
@Value("${disc.title}") String title,
@Value("${disc.artist}") String artist){
this.title = title;
this.artist = artist;
}
为了使用占位符, 我们必须要配置一个PropertySourcesPlaceholderConfigurer Bean.
例如如下的@Bean方法在Java中配置了PropertySourcesPlaceholderConfigurer:
@Configuration
public class PropertySourcesPlaceholderConfig {
@Bean
static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}
}
或者在XML配置文件中使用Spring context命名空间中的< context:property-placeholder>元素来生成PropertySourcesPlaceholderConfigurer。如下:
<?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"
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">
<context:property-placeholder />
</beans>
解析外部属性能够将值的处理推迟到运行时,但是它的关注点再与根据名称解析来自于Spring Environment和属性源的属性。而Spring表达式语言提供了一种更通用的方式在运行是计算所要注入的值。
使用Spring表达式语言进行值装配
Spring 3引入了Spring表达式语言,它能够以一种强大和简洁的方式将值装配到Bean属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算得到值。
spEL功能很强大,它有很多特性:
- 使用Bean的ID来引用Bean
- 调用方法和访问对象的属性
- 对值进行算数、关系和逻辑运算
- 正则表达式匹配
- 集合操作
spEL是一种非常灵活的表达式语言,它放在“#{…}”之中,与属性占位符有些类似。而{}里面的表达式体多种多样,简单的可以是一个数字,比如#{1};
复杂的可以是如下这样,最终结果为计算表达式的那一刻的当前时间的毫秒数。T()会将java.lang.System视为Java中对应的类型,因此可调用static修饰的currentTimeMillis()方法:
#{T(System).currentTimeMillis()}
spEL还可以引用其他的Bean或其他Bean的属性,如下计算ID为taylorSwift的Bean的artist属性:
#{taylorSwift.artist}
还可以通过systemProperties对象引用系统属性:
#{systemProperties['disc.title']}
也可以使用@Value注解,将里面的属性占位符替换为spEL表达式:
public TaylorSwift(
@Value("#{systemProperties['disc.title']}") String title,
@Value("#{systemProperties['disc.artist']}") String artist){
this.title = title;
this.artist = artist;
}
同理XML中也可以通过spEL表达式设置构造器参数:
<bean id = "idemo"
class = "soundsystem.TaylorSwift"
c:_title = "#{systemProperties['disc.title']}"
c:_artist = "#{systemProperties['disc.artist']}" />
接下来我们就来详细看看spEL所支持的基础表达式~
1)表示字面值
#{1} 表示整数
#{3.14159} 表示浮点数
#{9.87E4} 表示科学计数法
#{'HELLO'} 表示String类型
#{false} 表示boolean类型
2)引用Bean、属性和方法
引用Bean:
#{taylorSwift}
引用Bean的属性:
#{taylorSwift.artist}
调用Bean的方法:
#{taylorSwift.selectMusic()}
调用Bean方法返回值的方法:
#{taylorSwift.selectMusic().toUpperCase()}
在这个例子中,如果selectMusic()返回值不是null的话,再调用方法是没问题的,但是为了避免出现NullPointerException,可以使用类型安全的运算符,它会判断在访问右边内容前确保不是null,否则返回null:
#{taylorSwift.selectMusic()?.toUpperCase()}
3)在表达式中使用类
如果要在SpEL中访问类的方法或者常量的话,要使用T()这个关键的运算符。
比如为了在SpEL中表达Java的Math类,要按照下面的方式使用T()运算符:
T(java.lang.Math)
使用Math类的常量PI:
T(java.lang.Math).PI
调用Math类的方法:
T(java.lang.Math).random()
4)spEL运算符
运算符类型 | 运算符 |
---|---|
算术运算 | +、 -、 *、 /、 %、 ^ |
比较运算 | <、 >、 ==、 <=、 >=、 lt、 gt、 eq、 le、 ge |
逻辑运算 | and、 or、 not、| |
条件运算 | ?: (ternary)、? (Elvis) |
正则表达式 | matches |
计算圆的周长:
#{2 * T(java.lang.Math).PI * circle.radius}
计算圆的面积:
#{T(java.lang.Math).PI * circle.radius ^ 2}
String类的连接:
#{disc.title + ' by ' + disc.artist}
比较数字是否相等,结果返回boolean类型:
#{counter.total == 100}
#{counter.total eq 100}
三元运算符:
#{scoreboard.score > 1000 ? "Winner!" : "Loser"}
Elvis运算符,检查null值:
#{disc.title ?: 'Rattle and Hum'}
5)计算正则表达式
匹配邮箱:
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.com'}
6)计算集合
通过下标来获得集合或数组中某一元素的值:
#{taylorswift.songs[4].title}
从String中获取一个字符:
#{'hello world'[0]}
7)其他运算符
SpEL还提供了一些其它比较特殊的运算符~
查询运算符(.?[ ])
专门用来对集合进行过滤,得到集合的一个子集:
#{jukebox.songs.?[artist eq 'Aerosmith']}
查询运算符(.^[ ])和(.$[ ])
.^[ ]用来在集合中查询第一个匹配项
.$[ ]用来在集合中查询最后一个匹配项:
#{jukebox.songs.^[artist eq 'Aerosmith']}
#{jukebox.songs.$[artist eq 'Aerosmith']}
投影运算符(.![ ])
从集合的每个成员中选择特定的属性放到另一个集合中
#{jukebox.songs.![title]}
它还可以与其它SpEL运算符任意使用:
#{jukebox.songs.^[artist eq 'Aerosmith'].![title]}
以上就是spEL功能的一个皮毛。要注意的是,不要让表达式太复杂和智能,否则会给测试造成很多麻烦。我们要尽可能让表达式保持简洁。