1 應用程序環境的遷移
問題:
開發軟件時,有一個很大的挑戰,就是將應用程序從一個環境遷移到另一個環境。
例如,開發環境中很多方式的處理並不適合生產環境,遷移后需要修改,這個過程可能會莫名的出現很多bug,一個經常出現在程序員間有意思的問題是:在我那明明沒問題啊,為什么到你那就不行了?
舉個栗子,數據庫配置,在開發環境我們可能使用一個嵌入式的數據源並在啟動的時候加載進來,但是在生產環境中這是糟糕的做法,我們希望數據庫能直接從JNDI中獲取,或者是從連接池中獲取。那么問題來了,當從開發環境遷移到生產環境中時,我們應該怎么做?
一個很好想到的辦法是:配置多個xml文件,每個xml里面配置一種數據源。然后在構建階段,確定哪一種數據源編譯到可部署的應用中。
但是這樣的做法缺點要為每種環境重新構建應用,從開發階段遷移到QA(質量保證)階段肯能沒什么大問題,但是從QA遷移到生產階段從新構建可能會出現bug,還QA個毛啊~
解答:
Spring為環境遷移提供的解決方案是profile功能,為bean配置profile注解,然后激活對應的profile。
怎么配置profile?下面有兩種方式:
(1)基於Java配置
@Configuration public class DataSourceConfig { @Bean @Profile("dev") public DataSource embeddedDataSource() { // 配置開發環境 嵌入式數據源 } @Bean @Profile("prod") public DataSource jndiDataSource() { // 配置生產環境 JNDI數據源 } }
(2)基於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:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation=" http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <beans profile="dev"> <jdbc:embedded-database id="dataSource" type="H2"> <jdbc:script location="classpath:schema.sql" /> <jdbc:script location="classpath:test-data.sql" /> </jdbc:embedded-database> </beans> <beans profile="prod"> <jee:jndi-lookup id="dataSource" lazy-init="true" jndi-name="jdbc/myDatabase" resource-ref="true" proxy-interface="javax.sql.DataSource" /> </beans> </beans>
怎么激活profile?
Spring在確定哪個profile處於激活狀態時,需要依賴兩個獨立的屬性:spring.profiles.active和spring.profiles.default。
如果設置了spring.profiles.active屬性的話,那么它的值就會用來確定哪個profile是激活的。但如果沒有設置spring.profiles.active,那Spring將會查找spring.profiles.default的值。如果兩個屬性都沒配置,那么被@Profile注解的類都不會被加載。
有多種方式來設置這兩個屬性:
- 作為DispatcherServlet的初始化參數;
- 作為Web應用的上下文參數;
- 作為JNDI條目;
- 作為環境變量;
- 作為JVM的系統屬性;
- 在集成測試類上,使用@ActiveProfiles注解設置。
如何配置這些初始化參數不是本文討論的內容。但為了把例子解釋清楚,這里以web應用上下文參數為例梳理整個流程,配置如下:

這里默認激活配置為dev的profile,所以啟動應用時,以Java配置為例,注解@Profile("dev")標記的數據庫連接類會被加載到spring容器中,@Profile("prod")標記的類不會被加載。
profile是spring3.1中出現的新功能,只能用於環境遷移的條件裝配,但是spring4引入了Conditional功能,我們能靈活的處理條件化裝配bean了,而且spring4也使用Conditional重構了profile,也就是說spring4之后,prefile是基於Conditional實現的。
2 條件裝配bean
假如你希望一個或多個bean只有在應用的類路徑下包含特定的庫時才創建。或者我們希望某個bean只有當另外某個特定的bean也聲明了之后才會創建。我們還可能要求只有某個特定的環境變量設置之后,才會創建某個bean。這些使用Conditional都能實現。
舉個例子:假設有一個名為MagicBean的類,我們希望只有設置了magic環境屬性的時候,Spring才會實例化這個類。如果環境中沒有這個屬性,那么MagicBean將會被忽略。
首先要有一個MagicBean,我們不關注它的功能與實現,只需要知道有這個bean就行。接下來配置:
@Configuration public class MagicConfig { @Bean @Conditional(MagicExistsCondition.class) public MagicBean magicBean() { return new MagicBean(); } }
只有滿足MagicExistsCondition這個條件時,我們才實例化MagicBean。MagicExistsCondition需要實現Condition接口。Condition接口里面只有matches()方法,當實現類的matches()返回true時,條件才滿足。否則條件不滿足。MagicExistsCondition實現如下:
public class MagicExistsCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment env = context.getEnvironment(); return env.containsProperty("magic"); } }
只有環境變量magic存在,matches才會返回true,條件才滿足,MagicBean才會實例化。
參考文章《spring in action》
————完—————
