Spring Environment abstraction

Environment 是 Spring 容器中對于應(yīng)用環(huán)境兩個關(guān)鍵因素的一個抽象。它們分別是:Profilesproperties扒最。

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)境變量系草、JNDIServlet Context 參數(shù)唆涝、ad-hoc Properties 對象找都、Map 等等。EnvironmentProperties 的關(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è)置 profiledefault:

@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將不適用桩撮。

可以通過EnvironmentsetDefaultProfiles() 或者通過 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)前EnvironmentPropertySource集合中:

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類使用@propertysourceapp.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>

原文地址:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末齐邦,一起剝皮案震驚了整個濱河市椎侠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌措拇,老刑警劉巖我纪,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異儡羔,居然都是意外死亡宣羊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門汰蜘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仇冯,“玉大人,你說我怎么就攤上這事族操】良幔” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵色难,是天一觀的道長泼舱。 經(jīng)常有香客問我,道長枷莉,這世上最難降的妖魔是什么娇昙? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮笤妙,結(jié)果婚禮上冒掌,老公的妹妹穿的比我還像新娘。我一直安慰自己蹲盘,他們只是感情好股毫,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著召衔,像睡著了一般铃诬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上苍凛,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天趣席,我揣著相機(jī)與錄音,去河邊找鬼毫深。 笑死吩坝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哑蔫。 我是一名探鬼主播钉寝,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼弧呐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嵌纲?” 一聲冷哼從身側(cè)響起俘枫,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎逮走,沒想到半個月后鸠蚪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡师溅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年茅信,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片墓臭。...
    茶點(diǎn)故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡蘸鲸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窿锉,到底是詐尸還是另有隱情酌摇,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布嗡载,位于F島的核電站窑多,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏洼滚。R本人自食惡果不足惜埂息,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望遥巴。 院中可真熱鬧耿芹,春花似錦、人聲如沸挪哄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迹炼。三九已至,卻和暖如春颠毙,著一層夾襖步出監(jiān)牢的瞬間斯入,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工蛀蜜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刻两,地道東北人。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓滴某,卻偏偏與公主長得像磅摹,于是被迫代替她去往敵國和親滋迈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容