Environment 是 Spring 容器中對(duì)于應(yīng)用環(huán)境兩個(gè)關(guān)鍵因素的一個(gè)抽象。它們分別是:Profiles 和 properties。
Profile 是一個(gè) bean 定義命名的邏輯分組。容器中可以配置多個(gè) Profile,但是需要指定 profile 才會(huì)把這個(gè) Profile 下的分組 bean 定義注冊(cè)到容器中。 無論 beans 是定義在 XML 還是 注解里面都可以分配給一個(gè) Profile。Environment 對(duì)象與 Profile 關(guān)系是確定哪些 Profiles(if any)當(dāng)前是有效的,哪些 Profiles(如果有的話)應(yīng)該在默認(rèn)情況下是有效的。
Properties在幾乎所有應(yīng)用程序中都扮演著重要的角色,并且可能來自各種各樣的來源:properties 文件、JVM系統(tǒng)屬性、系統(tǒng)環(huán)境變量、JNDI、Servlet Context 參數(shù)、ad-hoc Properties 對(duì)象、Map 等等。Environment 與 Properties 的關(guān)系是為用戶提供一個(gè)方便的服務(wù)接口,用于配置屬性源并從它們中解析屬性。
1、Bean 定義 profiles
Bean定義 Profiles 是核心容器中的一種機(jī)制,它允許在不同的環(huán)境中注冊(cè)不同的 Bean。environment 這個(gè)詞對(duì)不同的用戶意味著不同的東西,這個(gè)特性可以在很多情況下非常有用,包括:
- 在開發(fā)環(huán)境中使用內(nèi)存中的數(shù)據(jù)源,在 QA 或生產(chǎn)環(huán)境中使用來自 JNDI 的數(shù)據(jù)源
- 只在將應(yīng)用程序部署到性能環(huán)境時(shí)才注冊(cè)監(jiān)測(cè)基礎(chǔ)設(shè)施
- 為客戶 A 和客戶 B 的部署注冊(cè)定制的bean實(shí)現(xiàn)
讓我們考慮一個(gè)實(shí)際應(yīng)用程序中的第一個(gè)用例,它需要一個(gè) DataSource。在測(cè)試環(huán)境中,配置可能如下:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
現(xiàn)在讓我們考慮一下這個(gè)應(yīng)用程序?qū)⑷绾尾渴鸬?QA 或生產(chǎn)環(huán)境中,假設(shè)應(yīng)用程序的數(shù)據(jù)源將在生產(chǎn)應(yīng)用服務(wù)器的 JNDI 目錄中注冊(cè)。我們dataSource 的 bean現(xiàn)在看起來是這樣的:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
問題是在當(dāng)前環(huán)境如何在這兩個(gè)變量之間切換進(jìn)行切換。隨著時(shí)間的推移,Spring 用戶已經(jīng)設(shè)計(jì)了許多方法來完成這項(xiàng)工作。通常依賴于系統(tǒng)環(huán)境變量和包含 ${placeholder}[占位符] 的 XML <import /> 代碼片段的組合。根據(jù)一個(gè)環(huán)境變量的值解析到正確配置文件路徑。Bean 定義 Profiles 是一個(gè)容器的核心特性,它為這個(gè)問題提供了解決方案。
@Profile
@Profile 注解允許你聲明把 Component 注冊(cè)一個(gè)或者多個(gè)指定的 Profile里面。使用上面的例子,我們可以重寫
dataSource配置如下:
@Configuration
@Profile("dev")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@profile可以用作元注解,目的是創(chuàng)建自定義組合注釋。下面的例子定義了一個(gè)自定義的@Production注釋,可以用來替換 @Profile("production"):
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
@profile也可以聲明在方法上來只包含一個(gè)特定的配置 bean :
@Configuration
public class AppConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean
@Profile("production")
public DataSource productionDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
XML bean 定義 profiles
與XML相應(yīng)的就是<beans> 標(biāo)簽里面的profile元素。上面的樣例配置可以在兩個(gè)XML文件中重寫:
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
同樣也可以避免在不同的文件中定義,可以在同一個(gè)文件中使用嵌套的 <beans /> 標(biāo)簽:
<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"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="development">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
spring-bean.xsd允許一個(gè)或者多個(gè) <beans />這樣的元素, 但是 <beans /> 元素只能和其它 <beans /> 元素平級(jí)。
這應(yīng)該有助于提供靈活性,而不會(huì)導(dǎo)致 XML 文件中的混亂。
合法的 Bean 定義
<beans>
<beans profile="development">
...
</beans>
<beans profile="production">
...
</beans>
</beans>
非法的 Bean 定義
<beans>
<bean />
<beans profile="development">
...
</beans>
<bean />
</beans>
激活 Profile
既然我們已經(jīng)更新了配置,我們?nèi)匀恍枰甘?Spring 哪個(gè)Profile是有效的。如果我們現(xiàn)在開始我們的樣例應(yīng)用程序,我們會(huì)看到拋出一個(gè)NoSuchBeanDefinitionException異常,因?yàn)槿萜鳠o法找到名為dataSource 的 Spring bean。
可以通過幾種方式進(jìn)行激活一個(gè)Profile,但是最直接的方法是通過編程的方式對(duì)ApplicationContext 里面的 Environment API進(jìn)行編程:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
此外,Profile 也可以通過spring.profile.active以聲明的方式被激活??赏ㄟ^系統(tǒng)環(huán)境變量、JVM系統(tǒng)屬性、web.xml里面的 ServletContext 參數(shù),
甚至是JNDI中的一個(gè) Entry(第2節(jié)“PropertySource abstraction”)。在集成測(cè)試中,可以通過spring-test模塊中的@activeprofiles注釋來激活 profile。
請(qǐng)注意,Profile 并不是一個(gè)“非此即為”的命題;是可以同時(shí)激活多個(gè)Profile 的??梢砸跃幊痰姆绞?通過調(diào)用setActiveProfiles()方法激活多個(gè) Profile。該方法接收可變參數(shù)字符串(String…):
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
可以通過spring.profiles.active來聲明,可以接收以逗號(hào)分隔的 profile 名稱列表:
-Dspring.profiles.active="profile1,profile2"
Default profile
一般情況下我們定義 bean 都沒有設(shè)置 profile,其實(shí) Spring 默認(rèn)幫我們?cè)O(shè)置 profile為 default:
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
如果沒有 Profile 被激活,則會(huì)創(chuàng)建上面的 dataSource;這可以看作是為一個(gè)或多個(gè) bean 提供缺省定義的一種方式。如果啟用了其它profile,則默認(rèn)的profile將不適用。
可以通過Environment 的 setDefaultProfiles() 或者通過 spring.profiles.default 屬性來改變默認(rèn)的 profile的名稱。
2、PropertySource abstraction
Spring 的Environment抽象提供了對(duì)屬性源的可配置層次結(jié)構(gòu)的搜索操作。為了充分解釋,請(qǐng)考慮以下例子:
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);
在上面的代碼片段中,我們看到了一種高級(jí)的詢問 Spring 的方式,即foo屬性是否為當(dāng)前環(huán)境定義。為了回答這個(gè)問題, Environment 對(duì)象會(huì)對(duì)一組 PropertySource 對(duì)象進(jìn)行搜索。PropertySource 是對(duì)任何來源的 key-value(鍵-值)對(duì)的簡單抽象.Spring的 StandardEnvironment 對(duì)象配置了兩個(gè) PropertySource 對(duì)象 —— 一個(gè)表示 JVM 系統(tǒng)屬性變量(通過 system.getproperties() 獲取),另一個(gè)代表系統(tǒng)環(huán)境變量(通過 system.getenv() 獲取)。
這些默認(rèn)的屬性源存在于
StandardEnvironmen中,用于獨(dú)立應(yīng)用程序。StandardServletEnvironment中包含了額外的默認(rèn)屬性源,包括servlet配置和servlet上下文參數(shù)。同樣,StandardPortletEnvironment也可以訪問portlet配置和portlet上下文參數(shù)作為屬性來源。兩者都可以選擇性地啟用JndiPropertySource。詳情請(qǐng)見javadocs。
具體地說,在使用 StandardEnvironment 時(shí),如果系統(tǒng)在運(yùn)行時(shí)系統(tǒng)屬性變量或系統(tǒng)環(huán)境變量包含foo屬性,那么調(diào)用env.containsProperty("foo")將返回 true。
屬性搜索是有層級(jí)結(jié)構(gòu)的。默認(rèn)情況下,系統(tǒng)屬性優(yōu)先于環(huán)境變量,所以如果
foo屬性碰巧在對(duì)env.getproperty(“foo”)的調(diào)用中同時(shí)設(shè)置,系統(tǒng)屬性值將“勝出”,并優(yōu)先于環(huán)境變量返回。請(qǐng)注意,屬性值不會(huì)被合并,而是被前面的條目完全覆蓋。
對(duì)于一個(gè)常見的 StandardServletEnvironment,完整的層次結(jié)構(gòu)如下所列,優(yōu)先級(jí)是由上而下的:
- ServletConfig parameters (例如在 DispatcherServlet 上下文)
- ServletContext parameters (web.xml 中 <context-param> 標(biāo)簽)
- JNDI environment variables ("java:comp/env/" 詞目)
- JVM system properties ("-D" 命令行參數(shù))
- JVM system environment (system 環(huán)境變量)
最重要的是,整個(gè)機(jī)制是可配置的。也許你有一個(gè)定制的屬性源,希望將其集成到這個(gè)搜索中。這個(gè)是沒有問題的——簡單地實(shí)現(xiàn)并實(shí)例化自己的 PropertySource,并將其添加到當(dāng)前Environment的 PropertySource集合中:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在上面的代碼中,MyPropertySource 在搜索中被添加了最高優(yōu)先級(jí)。如果它包含一個(gè)foo屬性,它將在任何其他 PropertySource 中的任何foo屬性之前被檢測(cè)并返回。MutablePropertySources API公開了許多方法,這些方法允許精確地操縱一組屬性源。
3、@PropertySource
@PropertySource注解提供了一種方便的聲明機(jī)制將 PropertySource 添加到 Spring 的 Environment中。
給定一個(gè)包含鍵/值對(duì)testbean.name=myTestBean的文件app.properties.下面的@configuration類使用@propertysource把 app.properties加載到 Spring 上下文中的Environment里面。然后調(diào)用estBean.getName()將返回myTestBean。
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
在@propertysource資源路徑位置上的也可以添加${...}占位符。它將根據(jù)已經(jīng)在環(huán)境中注冊(cè)的屬性源來解決。例如:
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
假設(shè)my.placeholder存在于已經(jīng)注冊(cè)的一個(gè)屬性源中,例如系統(tǒng)屬性或者系統(tǒng)環(huán)境變量,占位符將被解析為相應(yīng)的值。如果沒有,那么default/path將被用作默認(rèn)值。如果沒有指定默認(rèn)值,并且解析不到 property,則會(huì)拋出一個(gè)IllegalArgumentException 異常。
在Java 8特性中
@propertysource注釋是可重復(fù)的。然而,所有這些@propertysource注解都需要在相同的級(jí)別上聲明:要么直接在配置類上,要么在同一個(gè)定制注解中作為元注解。直接注釋和元注釋的混合是不推薦的,因?yàn)橹苯幼⑨寣⒏采w元注解。
4、Placeholder resolution in statements
在以前,elements 中的占位符的值只能通過JVM系統(tǒng)屬性或環(huán)境變量來解決?,F(xiàn)在不再是這種情況了。因?yàn)榄h(huán)境抽象是集成在整個(gè)容器中的,所以很容易通過它來解決占位符的解析。這意味著您可以以任何您喜歡的方式配置決議過程:更改通過系統(tǒng)屬性和環(huán)境變量進(jìn)行搜索的優(yōu)先級(jí),或者完全刪除它們;在適當(dāng)?shù)臅r(shí)候添加您自己的屬性源。
具體地說,不管用戶屬性的定義是什么,只要在環(huán)境中可用,下面的語句就可以工作:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
原文地址: