环境与profile
在不同的环境中某个bean会有所不同,我们必须要有一种方法来配置,使其在每种环境下都会选择最为合适的配置。
配置profile bean
在构建bean的过程中需要根据环境决定哪个bean创建、哪个bean不创建。不过spring并不是在构建的时候做出这样的决策,而是等到运行时再来确定。
spring3.1引入bean profile功能,将所有不同的bean定义整理到一个或多个profile中,再将应用部署到每个环境中,要确保对应的profile处于激活状态。
使用注解@Profile
,它会告诉spring这个配置类中的bean只有在dev profile激活时才会创建,如果dev profile没有激活时,那么带有@Bean的注解的方法都会被忽略掉。
同时,可能还需要有一个适用于生产环境的配置。
spring3.2开始,可以再方法级别上使用@Profile
注解,与@Bean注解一同使用。
同样的,只有当规定的profile激活时,相应的bean才会被创建。但是如果没有指定profile的bean不管哪种情况都会被创建。
激活profile
spring中确定激活哪个profile取决于两个独立的属性spring.profiles.active
和spring.profiles.default
。如果设置了spring.profiles.active
属性的话,那么它的值就会用来确定那个profile是激活的,但是如果没有设置该属性,那么将会检查spring.profiles.default
的值,如果均没有设置的话,那就没有激活profile,因此只会创建那些没有定义在profile中的bean。
设置方式:
- 作为DispatcherServlet的初始化参数;
- 作为JNDI条目;
- 作为环境变量;
- 作为JVM的系统属性;
- 在集成测试类上,使用
@ActiveProfiles
注解设置;
你可以同时激活多个profile,列出多个profile名称,并以逗号分隔来实现。
条件化的bean
spring4引入了一个新的@Conditional
注解,它可以用到带有@Bean注解的方法上,如果给定条件的计算结果为true,就会创建这个bean,否则这个bean会被忽略。
设置给@Conditional
的类可以是任意实现了Condition接口的类型,对于这个借口只需提供matches()方法即可,如果这个方法返回true,就会创建被注解的bean,否则不会创建。
ConditionContext
能够实现以下功能:
getRegistry()
返回的BeanDefinitionRegistry
检查bean定义;getBeanFactory()
返回的ConfigurationListableBeanFactory
检查bean是否存在,探查bean的属性。getEnvironment()
返回的Environment检查环境变量是否存在以及它的值是什么;getResourceLoader()
返回的ResourceLoader所加载的资源;getClassLoader()
返回的ClassLoader加载并检查类是否存在。
AnnotatedTypeMetadata
能够让我们检查带有@bean注解的方法上还有什么其他注解。
其中isAnnotated()
方法能够判断@bean注解的方法是不是还有其他注解。
@Profile
本身也使用了@Condition
注解,并且引用ProfileCondition
作为Condition
的实现。
ProfileCondition通过AnnotatedTypeMetadata得到了用于@Profile注解的所有属性。借助该信息,它会明确检查value属性,该属性包含了bean的profile名称,根据Environment来检查该profile是否处于激活状态。
处理自动装配的歧义性
自动装配让spring完全负责将bean引用注入到构造函数参数和属性中,自动装配能够提供很大的帮助,因为它会减少装配应用程序组件时所需要的显式配置的数量。
但是,如果有不止一个bean能够匹配结果的话,这种歧义性会阻碍spring自动装配属性、构造器函数或方法参数。
public interface Dessert {
void printf();
}
@Component
public class Cake implements Dessert{
public void printf() {
System.out.println("I like Cake");
}
}
@Component
public class Cookies implements Dessert{
public void printf() {
System.out.println("I like Cookies");
}
}
@Component
public class FoodService {
Dessert dessert;
@Autowired
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
public Dessert getDessert() {
return dessert;
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Appconfig.class);
FoodService bean = context.getBean(FoodService.class);
bean.getDessert().printf();
}
当确实发生歧义的时候,spring提供了多种可选方案来解决。
标示首选的bean @Primary
在声明bean的时候,通过将其中一个可选的bean设置为首选bean能够避免自动装配时的歧义性。
可以通过@Primary
来表达最喜欢的方案:
@Component
@Primary
public class Cookies implements Dessert{
public void printf() {
System.out.println("I like Cookies");
}
}
@Primary
能够与@Comonent
组合用在组件扫描bean上,也可以与@Bean
组合用在Java配置的bean声明中、
限定自动装配的bean @Qualifier
设置首选bean的局限性在与无法将可选方案的范围限定到唯一一个无歧义性的选项中。并且智能有限标示一个可选方案,首选bean的数量超过一个时。我们并没有其他方法进一步缩小可选范围。
与之相反,spring的限定符能够自所有可选的bean上进行缩小范围的操作。如果将所有限定符都用上后依然存在歧义性,那么可以继续使用更多限定符来缩小选择范围。
@Qualifier
是使用限定符的主要方式,可以与@Autowired``@Inject
协同使用,在注入的时候指定想要注入进去的是哪个bean。
@Autowired
@Qualifier("cake")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
@Qualifier
所设置的参数就是要注入Bean的ID,所有使用@Component
注解声明的类都会创建为bean,并且bean的默认ID为首字母变小写的类名。
但是,如果bean的ID和默认限定符变了的话,就无法匹配,自动装配就会失败,这里问题在于setDessert
方法上所指定的限定符与要注入的bean的名称是紧耦合的,对类名称的任意改动都会导致限定符失败。
创建自定义限定符
可以为bean设置自己的限定符,而不依赖与默认的ID。
@Component
@Qualifier(value = "hello")
public class Cake implements Dessert{
public void printf() {
System.out.println("I like Cake");
}
}
@Autowired
@Qualifier("hello")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
如果有了两个带有同样限定符的bean,在自动装配时会再次遇到歧义性问题,需要更多的限定符来将可选范围限定到只有一个bean。然而Java不允许在同一个条目上重复出现相同类型的多个注解。
但是我们可以创建自定义的限定符注解。
bean的作用域
默认情况下,spring上下文中所有的bean都是作为单例形式创建的,大多数情况下,单例单例是很理想的方案,但是有时候会发现你所使用的类是易变的。
spring定义了多种作用域,可以基于这些作用域创建bean
- 单例:在整个应用中,只创建bean的一个实例;
- 原型:每次注入或者通过spring上下文获取的时候,都会创建一个新的bean;
- 会话:为每个会话创建一个bean实例;
- 请求:为每个请求创建一个bean实例;
单例时默认的作用域,如果要选择其他作用域要使用@Scope
注解。
这里使用ConfigurableBeanFactory
;类的SCOPE_PROTOTYPE
设置原型作用域。
使用会话和请求作用域
会话作用域
对于给定会话只会创建一个实例,在当前会话相关操作中,bean相当于单例。
值得注意的是,@Scope
同时还有一个proxyMode属性,它被设置成了@Scope(proxyMode = ScopedProxyMode.INTERFACES)
这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。
spring并不会将实际的ShoppingCart Bean注入StoreService,而是注入一个代理,当StoreService调用ShoppingCart方法是,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart Bean。
如果ShoppingCart是接口的话,可以使用ScopedProxyMode.INTERFACES
,但如果是一个具体的类的话,spring就没办法常见基于接口的代理了,此时,必须使用CGLib来生成基于类的代理,所以使用ScopedProxyMode.TARGET_CLASS
来表明要生成目标类扩展的方式创建代理。
请求作用域的bean也以作用域代理的方式进行注入。
运行时值注入
spring提供了两种在运行时求值得方式:
- 属性占位符;
- spring表达式语言;
注入外部的值
在spring中,处理外部值最简单方式就是声明属性源并通过spring的environment来检索属性值。
app.properties
disc.title=Hello World
disc.artist=The Beatles
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
@ToString
public class BlankDisc {
private String titile;
private String artist;
}
@Configuration
@PropertySource("classpath:/app.properties") //声明属性源
public class ExpressiveConfig {
@Autowired
Environment environment;
public BlankDisc disc(){ // 检查属性值
BlankDisc blankDisc = new BlankDisc(environment.getProperty("disc.title")
, environment.getProperty("disc.artist"));
System.out.println(blankDisc.toString());
return blankDisc;
}
}
ExpressiveConfig bean = context.getBean(ExpressiveConfig.class);
bean.disc();
属性文件会加载到spring的Environment中,稍后从这里检索属性。
深入学习Spring的Environment
String getProperty(String key);
/**
* Return the property value associated with the given key, or
* {@code defaultValue} if the key cannot be resolved.
* 如果为获取到key对应的值,则返回默认值
*/
String getProperty(String key, String defaultValue);
/**
* Return the property value associated with the given key,
* or {@code null} if the key cannot be resolved.
* 返回指定类型的值
*/
<T> T getProperty(String key, Class<T> targetType);
/**
* Return the property value associated with the given key,
* or {@code defaultValue} if the key cannot be resolved.
*/
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
/**
* 如果给定的key无法解析则抛出异常
* @throws IllegalStateException
*/
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
/**
* 判断给定的key是否可以解析
*/
boolean containsProperty(String key);
/**
* 将属性解析为类
*/
@Deprecated
<T> Class<T> getPropertyAsClass(String key, Class<T> targetType);
解析属性占位符
spring一直支持将属性定义到外部的属性文件中,并使用占位符值将其插入到spring bean中,在spring装配中,占位符使用${XXX}
包装的属性名称。
public BlankDisc(@Value("${disc.title}") String title, @Value("${disc.artist}") String artist) {
this.title = title;
this.artist = artist;
}
spring表达式语言进行装配
spring3引入了spring表达式语言(Spring Expression Language,SpEL),能够将值装配到bean属性和构造器参数中,表达式会在运行时计算得到值。
SpEL表达式要放到#{...}
中;