本节主要讲:
- Spring profile
- 自动装配与歧义性
- Spring表达式语言
Spring profile
当需要根据环境决定该创建哪个 bean 和不创建哪个 bean,可以使用bean profile 的功能,确保是等到运行时再来确定,这样的结果就是同一个部署单元(可能会是 WAR 文件)能够适用于所有的环境,没有必要进行重新构建。
配置profile bean
在 Java 配置中,可以使用 @Profile 注解指定某个 bean 属于哪一个 profile,例如,在配置类中,嵌入式数据库的 DataSource 可能会配置成如下所示:
package com.myapp;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;
@Configuration
public class DataSourceConfig {
@Bean(destroyMethod = "shutdown")
@Profile("dev")
public DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
@Bean
@Profile("prod")
public DataSource jndiDataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}
激活 profile
有多种方式来设置这两个属性:
- 作为 DispatcherServlet 的初始化参数;
- 作为 Web 应用的上下文参数;
- 作为 JNDI 条目;
- 作为环境变量;
- 作为 JVM 的系统属性;
- 在集成测试类上,使用 @ActiveProfiles 注解设置。
处理自动装配的歧义性
仅有一个 bean 匹配所需的结果时,自动装配才是有效的。如果不仅有一个 bean 能够匹配结果的话,这种歧义性会阻碍 Spring 自动装配属性、构造器参数或方法参数。
标示首选的 bean
采用@Primary只能标记一个首选bean,如果你标示了两个或更多的首选 bean,那么它就无法正常工作了。
package com.food;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component
@Primary
public class Cake implements Dessert {
}
限定自动装配的 bean
设置首选 bean 的局限性在于 @Primary 无法将可选方案的范围限定到唯一一个无歧义性的选项中。它只能标示一个优先的可选方案。当首选 bean 的数量超过一个时,我们并没有其他的方法进一步缩小可选范围。
我们这里使用自定义的限定符注解。当使用自定义的 @Qualifier 值时,最佳实践是为 bean 选择特征性或描述性的术语,而不是使用随意的名字。
创建自定义的限定符注解,这里要使用@Qualifier来标注一下:
package com.food;
import javax.inject.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {
}
为组件添加@Cold注解:
package com.food;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Cold
public class IceCream implements Dessert {
}
为了得到 IceCream bean,DiningTable() 方法可以这样使用注解:
package com.food;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class DiningTable {
private Dessert dessert;
@Autowired
@Creamy
public DiningTable(Dessert dessert) {
this.dessert = dessert;
}
}
运行时值注入
当讨论依赖注入的时候,我们通常所讨论的是将一个 bean 引用注入到另一个 bean 的属性或构造器参数中,但是 bean 装配的另外一个方面指的是将一个值注入到 bean 的属性或者构造器参数中。
例如:
@Bean
public CompactDisc sgtPeppers() {
return new BlankDisc(
"Sgt. Pepper's Lonely Hearts Club Band",
"The Beatles"
);
}
BlankDisc bean 设置 title 和 artist,但它在实现的时候是将值硬编码在配置类中的,有时候硬编码是可以的,但有的时候,我们可能会希望避免硬编码值,而是想让这些值在运行时再确定。
Spring提供了两种在运行时求值的方式:
- 属性占位符。
- Spring 表达式语言(SpEL)。
属性占位符
属性占位符需要放到 ${ ... }
之中。
在本例中,@PropertySource 引用了类路径中一个名为 app.properties 的文件。它大致会如下所示:
disc.title=Sgt. Peppers Lonely Hearts Club
disc.artisc=The Beatles
如果我们依赖于组件扫描和自动装配来创建和初始化应用组件的话,那么就没有指定占位符的配置文件或类了。比如,在 BlankDisc 类中,构造器可以改成如下所示:
public BlankDisc(@Value("${disc.title}") String title, @Value("${disc.artist}") String artist) {
this.title = title;
this.artist = artist;
}
@Value的使用方式与 @Autowired 注解非常相似。
使用 Spring 表达式语言进行装配
SpEL 拥有很多特性,包括:
- 表示字面值;
- 使用 bean 的 ID 来引用 bean;
- 调用方法和访问对象的属性;
- 对值进行算术、关系和逻辑运算;
- 正则表达式匹配;
- 集合操作。
SpEL样例
需要了解的第一件事情就是 SpEL 表达式要放到 #{ ... }
之中。
如果通过组件扫描创建 bean 的话,在注入属性和构造器参数时,我们可以使用 @Value 注解,例如:
public BlankDisc(@Value("#{systemProperties['disc.title']}") String title, @Value("#{systemProperties['disc.artist']}") String artist) {
this.title = title;
this.artist = artist;
}
表示字面值
#{3.14159}
#{false}
#{'3.14159'}
#{9.78E4}//科学计数法
引用 bean、属性和方法
#{sgtPeppers}//通过 ID 引用其他的 bean
#{sgtPeppers.artist}//引用sgtPeppers的artist属性
#{sgtPeppers.selectArtist()}//引用sgtPeppers的方法
#{sgtPeppers.selectArtist()?.toUpperCase()}//?.这个运算符能够在访问它右边的内容之前,确保它所对应的元素不是 null。所以,如果 selectArtist() 的返回值是 null 的话,那么 SpEL 将不会调用 toUpperCase() 方法。表达式的返回值会是 null。
在表达式中使用类型
T(java.lang.Math)//这里所示的 T() 运算符的结果会是一个 Class 对象,代表了 java.lang.Math。
T()
运算符的真正价值在于它能够访问目标类型的静态方法和常量。
T(java.lang.Math).PI
T(java.lang.Math).random()
计算正则表达式
matches 返回的是一个boolean类型的值
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}
计算集合
//引用一个jukebox bean的一个songs集合的title属性
#{jukebox.songs[4].title}
//.?[] 运算符用来对集合进行过滤,得到集合的一个子集
//得到jukebox 中 artist 属性为 Aerosmith 的所有歌曲
#{jukebox.songs.?[artist eq 'Aerosmith']}
//.^[] 用在集合中查询第一个匹配项
#{jukebox.songs.^[artist eq 'Aerosmith']}
//.$[] 用在集合中查询最后一个匹配项
#{jukebox.songs.$[artist eq 'Aerosmith']}
//.![]从集合的每个成员中选择特定的属性放到另外一个集合中
#{jukebox.songs.![title]}