根據不同的環境來裝配不同的bean
企業級開發中,我們一般有多種環境,比如開發環境、測試環境、UAT環境和生產環境。而系統中有些配置是和環境強相關的,比如數據庫相關的配置,與其他外部系統的集成等。
如何才能實現一個部署包適用於多種環境呢?
Spring給我們提供了一種解決方案,這便是條件化裝配bean的機制。最重要的是這種機制是在運行時決定該注入適用於哪個環境的bean對象,不需要重新編譯構建。
下面使用Spring的profile機制實現dataSource對象的條件化裝配。
1、給出開發環境、測試環境、生產環境dataSource的不同實現類
說明:此處只為演示條件化裝配bean,不做真實數據源對象模擬。
public interface DataSource {
void show();
}
public class DevDataSource implements DataSource{
public DevDataSource(){
show();
}
public void show() {
System.out.println("開發環境數據源對象");
}
}
public class TestDataSource implements DataSource{
public TestDataSource() {
show();
}
public void show() {
System.out.println("測試環境數據源對象");
}
}
public class ProDataSource implements DataSource{
public ProDataSource() {
show();
}
public void show() {
System.out.println("生產環境數據源對象");
}
}
2、使用profile配置條件化bean
其實profile的原理就是將不同的bean定義綁定到一個或多個profile之中,在將應用部署到不同的環境時,確保對應的profile處於激活狀態即可。
這里我們使用JavaConfig的方式配置profile bean
@Configuration
public class DataSourceConfig {
@Bean
@Profile("dev")
public DataSource devDataSource(){
return new DevDataSource();
}
@Bean
@Profile("test")
public DataSource testDataSource(){
return new TestDataSource();
}
@Bean
@Profile("pro")
public DataSource proDataSource(){
return new ProDataSource();
}
}
可以看到我們使用了@Profile注解,將不同環境的bean綁定到了不同的profile中。
3、激活profile
只要上面的兩步還不行,我們還必須激活profile,這樣Spring會依據激活的哪個profile,來創建並裝配對應的bean對象。
激活profile需要兩個屬性。
spring.profiles.active
spring.profiles.default
可以在web.xml中配置Web應用的上下文參數,來激活profile屬性。比如在web.xml中增加如下配置來激活dev的profile:
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>dev</param-value>
</context-param>
4、測試條件化裝配
使用@ActiveProfiles注解在測試類中激活指定profile。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {DataSourceConfig.class})
@ActiveProfiles("dev")
public class TestConditionDataSource {
@Autowired
private DataSource dataSource;
@Test
public void testDataSource(){
Assert.assertNotNull(dataSource);
}
}
輸出:
開發環境數據源對象
我們profile換成生產環境的pro試下,
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {DataSourceConfig.class})
@ActiveProfiles("pro")
public class TestConditionDataSource {
@Autowired
private DataSource dataSource;
@Test
public void testDataSource(){
Assert.assertNotNull(dataSource);
}
}
輸出:
生產環境數據源對象
通過spring的profile機制,我們實現了不同環境dataSource數據源對象的條件化裝配。比較簡單,就兩步:1、使用@Profile注解為不同的bean配置profile(當然這里也可以是xml的方式),2、根據不同環境激活不同的profile。
使用@Conditional注解實現條件化的bean
Spring 4.0引入的新注解@Conditional注解,它可以用到帶有@Bean注解的方法上,如果給定的條件計算結果為true,就會創建這個bean,否則不創建。
1、我們創建一個helloWorld對象
public class HelloWorld {
public void sayHello(){
System.out.println("conditional 裝配helloworld");
}
}
2、創建配置類
在該配置類中我們首先使用了@PropertySource注解加載了屬性文件hello.properties,其次可以看到在helloWorld的bean配置中,除了@Bean注解外,多了一個@Conditional注解,不錯,@Conditional注解是我們實現條件化裝配bean的核心注解。
@Conditional注解中有一個HelloWorldConditional類,該類定義了我們創建該bean對象的條件。
@Configuration
@PropertySource("classpath:hello.properties")
public class HelloWorldConfig {
@Bean
@Conditional(HelloWorldConditional.class)
public HelloWorld helloWorld(){
return new HelloWorld();
}
}
3、創建條件類HelloWorldConditional,需要實現Condition接口。
實現了Condition接口,重寫了matches方法,在該方法中我們檢測了環境變量中是否有hello屬性,如果有就創建。沒有則忽略。
注意:hello.properties中屬性會存儲到spring的Environment對象中,因此我們可以檢測到其中的屬性是否存在。
public class HelloWorldConditional implements Condition {
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return conditionContext.getEnvironment().containsProperty("hello");
}
}
4、測試條件裝配
public class HelloWorldConditionTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(HelloWorldConfig.class);
HelloWorld helloWorld = applicationContext.getBean("helloWorld",HelloWorld.class);
helloWorld.sayHello();
}
}
開始,我們在hello.properties中增加一條屬性,

運行測試示例,會輸出:
conditional 裝配helloworld
說明此時,bean已成功裝配。
如果我們注釋掉hello.properties的這行屬性。再次運行示例,則會提示bean不存在。

提示沒有“helloWorld”的bean對象,說明了條件不滿足不會創建bean對象。

總結
Spring條件化裝配bean的兩種方式,第一種是使用profile機制,在bean的配置類中使用@profile注解,標識哪些bean對應哪個profile配置,然后在web.xml或Servlet啟動參數中配置激活哪個profile來實現條件裝配;第二種是使用@Conditional注解,在帶有@Bean注解的方法上增加@Conditional注解,在注解屬性值中提供一個實現了Condition接口的類(該類會重寫matches方法,定義具體的創建條件)。<完>

