Environment
是 Spring 容器中對于應(yīng)用環(huán)境兩個關(guān)鍵因素的一個抽象。它們分別是:Profiles
和 properties
扒最。
Profile
是一個 bean 定義命名的邏輯分組某弦。容器中可以配置多個 Profile
,但是需要指定 profile
才會把這個 Profile
下的分組 bean 定義注冊到容器中。 無論 beans 是定義在 XML 還是 注解里面都可以分配給一個 Profile
碉克。Environment
對象與 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 對象
找都、Map
等等。Environment
與 Properties
的關(guān)系是為用戶提供一個方便的服務(wù)接口廊酣,用于配置屬性源并從它們中解析屬性能耻。
1、Bean 定義 profiles
Bean定義 Profiles
是核心容器中的一種機(jī)制,它允許在不同的環(huán)境中注冊不同的 Bean晓猛。environment
這個詞對不同的用戶意味著不同的東西饿幅,這個特性可以在很多情況下非常有用,包括:
- 在開發(fā)環(huán)境中使用內(nèi)存中的數(shù)據(jù)源,在 QA 或生產(chǎn)環(huán)境中使用來自 JNDI 的數(shù)據(jù)源
- 只在將應(yīng)用程序部署到性能環(huán)境時才注冊監(jiān)測基礎(chǔ)設(shè)施
- 為客戶 A 和客戶 B 的部署注冊定制的bean實現(xiàn)
讓我們考慮一個實際應(yīng)用程序中的第一個用例戒职,它需要一個 DataSource
栗恩。在測試環(huán)境中,配置可能如下:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
現(xiàn)在讓我們考慮一下這個應(yīng)用程序?qū)⑷绾尾渴鸬?QA 或生產(chǎn)環(huán)境中洪燥,假設(shè)應(yīng)用程序的數(shù)據(jù)源將在生產(chǎn)應(yīng)用服務(wù)器的 JNDI 目錄中注冊磕秤。我們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)境如何在這兩個變量之間切換進(jìn)行切換。隨著時間的推移蚓曼,Spring 用戶已經(jīng)設(shè)計了許多方法來完成這項工作亲澡。通常依賴于系統(tǒng)環(huán)境變量和包含 ${placeholder}[占位符]
的 XML <import />
代碼片段的組合。根據(jù)一個環(huán)境變量的值解析到正確配置文件路徑纫版。Bean 定義 Profiles
是一個容器的核心特性,它為這個問題提供了解決方案客情。
@Profile
@Profile 注解允許你聲明把 Component 注冊一個或者多個指定的 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)建自定義組合注釋膀斋。下面的例子定義了一個自定義的@Production
注釋梭伐,可以用來替換 @Profile("production")
:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
@profile
也可以聲明在方法上來只包含一個特定的配置 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
元素。上面的樣例配置可以在兩個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>
同樣也可以避免在不同的文件中定義,可以在同一個文件中使用嵌套的 <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
允許一個或者多個 <beans />
這樣的元素, 但是 <beans />
元素只能和其它 <beans />
元素平級仰担。
這應(yīng)該有助于提供靈活性,而不會導(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 哪個Profile
是有效的摔蓝。如果我們現(xiàn)在開始我們的樣例應(yīng)用程序赂苗,我們會看到拋出一個NoSuchBeanDefinitionException
異常,因為容器無法找到名為dataSource
的 Spring bean贮尉。
可以通過幾種方式進(jìn)行激活一個Profile
拌滋,但是最直接的方法是通過編程的方式對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中的一個 Entry(第2節(jié)“PropertySource abstraction”)。在集成測試中览芳,可以通過spring-test
模塊中的@activeprofiles
注釋來激活 profile
斜姥。
請注意,Profile
并不是一個“非此即為”的命題;是可以同時激活多個Profile
的〖部剩可以以編程的方式,通過調(diào)用setActiveProfiles()
方法激活多個 Profile千贯。該方法接收可變參數(shù)字符串(String…)
:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
可以通過spring.profiles.active
來聲明,可以接收以逗號分隔的 profile
名稱列表:
-Dspring.profiles.active="profile1,profile2"
Default profile
一般情況下我們定義 bean 都沒有設(shè)置 profile
,其實 Spring 默認(rèn)幫我們設(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
被激活搞坝,則會創(chuàng)建上面的 dataSource
;這可以看作是為一個或多個 bean 提供缺省定義的一種方式搔谴。如果啟用了其它profile
,則默認(rèn)的profile
將不適用桩撮。
可以通過Environment
的 setDefaultProfiles()
或者通過 spring.profiles.default
屬性來改變默認(rèn)的 profile
的名稱敦第。
2、PropertySource abstraction
Spring 的Environment
抽象提供了對屬性源的可配置層次結(jié)構(gòu)的搜索操作店量。為了充分解釋芜果,請考慮以下例子:
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);
在上面的代碼片段中,我們看到了一種高級的詢問 Spring 的方式融师,即foo
屬性是否為當(dāng)前環(huán)境定義右钾。為了回答這個問題, Environment
對象會對一組 PropertySource
對象進(jìn)行搜索。PropertySource
是對任何來源的 key-value(鍵-值)
對的簡單抽象.Spring的 StandardEnvironment
對象配置了兩個 PropertySource
對象 —— 一個表示 JVM 系統(tǒng)屬性變量(通過 system.getproperties() 獲取
),另一個代表系統(tǒng)環(huán)境變量(通過 system.getenv() 獲取)
旱爆。
這些默認(rèn)的屬性源存在于
StandardEnvironmen
中舀射,用于獨(dú)立應(yīng)用程序。StandardServletEnvironment
中包含了額外的默認(rèn)屬性源怀伦,包括servlet配置
和servlet上下文參數(shù)
脆烟。同樣,StandardPortletEnvironment
也可以訪問portlet
配置和portlet上下文參數(shù)
作為屬性來源房待。兩者都可以選擇性地啟用JndiPropertySource
邢羔。詳情請見javadocs
。
具體地說桑孩,在使用 StandardEnvironment
時拜鹤,如果系統(tǒng)在運(yùn)行時系統(tǒng)屬性變量或系統(tǒng)環(huán)境變量包含foo
屬性,那么調(diào)用env.containsProperty("foo")
將返回 true
。
屬性搜索是有層級結(jié)構(gòu)的洼怔。默認(rèn)情況下署惯,系統(tǒng)屬性優(yōu)先于環(huán)境變量,所以如果
foo
屬性碰巧在對env.getproperty(“foo”)
的調(diào)用中同時設(shè)置镣隶,系統(tǒng)屬性值將“勝出”极谊,并優(yōu)先于環(huán)境變量返回。請注意安岂,屬性值不會被合并轻猖,而是被前面的條目完全覆蓋。
對于一個常見的 StandardServletEnvironment
域那,完整的層次結(jié)構(gòu)如下所列咙边,優(yōu)先級是由上而下的:
- 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)境變量)
最重要的是猜煮,整個機(jī)制是可配置的。也許你有一個定制的屬性源败许,希望將其集成到這個搜索中王带。這個是沒有問題的——簡單地實現(xiàn)并實例化自己的 PropertySource
,并將其添加到當(dāng)前Environment
的 PropertySource
集合中:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在上面的代碼中市殷,MyPropertySource
在搜索中被添加了最高優(yōu)先級愕撰。如果它包含一個foo
屬性,它將在任何其他 PropertySource
中的任何foo
屬性之前被檢測并返回醋寝。MutablePropertySources API
公開了許多方法搞挣,這些方法允許精確地操縱一組屬性源。
3音羞、@PropertySource
@PropertySource
注解提供了一種方便的聲明機(jī)制將 PropertySource
添加到 Spring 的 Environment
中囱桨。
給定一個包含鍵/值對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)境中注冊的屬性源來解決。例如:
@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)注冊的一個屬性源中窘面,例如系統(tǒng)屬性或者系統(tǒng)環(huán)境變量貌夕,占位符將被解析為相應(yīng)的值。如果沒有民镜,那么default/path
將被用作默認(rèn)值。如果沒有指定默認(rèn)值,并且解析不到 property险毁,則會拋出一個IllegalArgumentException
異常制圈。
在Java 8特性中
@propertysource
注釋是可重復(fù)的。然而畔况,所有這些@propertysource
注解都需要在相同的級別上聲明:要么直接在配置類上鲸鹦,要么在同一個定制注解中作為元注解。直接注釋和元注釋的混合是不推薦的跷跪,因為直接注釋將覆蓋元注解馋嗜。
4、Placeholder resolution in statements
在以前吵瞻,elements 中的占位符的值只能通過JVM系統(tǒng)屬性或環(huán)境變量來解決「鸸剑現(xiàn)在不再是這種情況了。因為環(huán)境抽象是集成在整個容器中的橡羞,所以很容易通過它來解決占位符的解析眯停。這意味著您可以以任何您喜歡的方式配置決議過程:更改通過系統(tǒng)屬性和環(huán)境變量進(jìn)行搜索的優(yōu)先級,或者完全刪除它們;在適當(dāng)?shù)臅r候添加您自己的屬性源卿泽。
具體地說莺债,不管用戶屬性的定義是什么,只要在環(huán)境中可用,下面的語句就可以工作:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
原文地址: