Spring Environment abstraction

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

Profile 是一個 bean 定義命名的邏輯分組。容器中可以配置多個 Profile,但是需要指定 profile 才會把這個 Profile 下的分組 bean 定義注冊到容器中。 無論 beans 是定義在 XML 還是 注解里面都可以分配給一個 Profile。Environment 對象與 Profile 關系是確定哪些 Profiles(if any)當前是有效的,哪些 Profiles(如果有的話)應該在默認情況下是有效的。

Properties在幾乎所有應用程序中都扮演著重要的角色,并且可能來自各種各樣的來源:properties 文件、JVM系統(tǒng)屬性、系統(tǒng)環(huán)境變量、JNDI、Servlet Context 參數(shù)、ad-hoc Properties 對象Map 等等。EnvironmentProperties 的關系是為用戶提供一個方便的服務接口,用于配置屬性源并從它們中解析屬性。

1、Bean 定義 profiles

Bean定義 Profiles 是核心容器中的一種機制,它允許在不同的環(huán)境中注冊不同的 Bean。environment 這個詞對不同的用戶意味著不同的東西,這個特性可以在很多情況下非常有用,包括:

  • 在開發(fā)環(huán)境中使用內(nèi)存中的數(shù)據(jù)源,在 QA 或生產(chǎn)環(huán)境中使用來自 JNDI 的數(shù)據(jù)源
  • 只在將應用程序部署到性能環(huán)境時才注冊監(jiān)測基礎設施
  • 為客戶 A 和客戶 B 的部署注冊定制的bean實現(xiàn)

讓我們考慮一個實際應用程序中的第一個用例,它需要一個 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)在讓我們考慮一下這個應用程序將如何部署到 QA 或生產(chǎn)環(huán)境中,假設應用程序的數(shù)據(jù)源將在生產(chǎn)應用服務器的 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");
}

問題是在當前環(huán)境如何在這兩個變量之間切換進行切換。隨著時間的推移,Spring 用戶已經(jīng)設計了許多方法來完成這項工作。通常依賴于系統(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相應的就是<beans> 標簽里面的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 /> 標簽:

<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 /> 元素平級。
這應該有助于提供靈活性,而不會導致 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)在開始我們的樣例應用程序,我們會看到拋出一個NoSuchBeanDefinitionException異常,因為容器無法找到名為dataSource 的 Spring bean。

可以通過幾種方式進行激活一個Profile,但是最直接的方法是通過編程的方式對ApplicationContext 里面的 Environment API進行編程:

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 的。可以以編程的方式,通過調用setActiveProfiles()方法激活多個 Profile。該方法接收可變參數(shù)字符串(String…)

ctx.getEnvironment().setActiveProfiles("profile1", "profile2");

可以通過spring.profiles.active來聲明,可以接收以逗號分隔的 profile 名稱列表:

-Dspring.profiles.active="profile1,profile2"

Default profile

一般情況下我們定義 bean 都沒有設置 profile,其實 Spring 默認幫我們設置 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,則默認的profile將不適用。

可以通過EnvironmentsetDefaultProfiles() 或者通過 spring.profiles.default 屬性來改變默認的 profile的名稱。

2、PropertySource abstraction

Spring 的Environment抽象提供了對屬性源的可配置層次結構的搜索操作。為了充分解釋,請考慮以下例子:

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屬性是否為當前環(huán)境定義。為了回答這個問題, Environment 對象會對一組 PropertySource 對象進行搜索。PropertySource 是對任何來源的 key-value(鍵-值)對的簡單抽象.Spring的 StandardEnvironment 對象配置了兩個 PropertySource 對象 —— 一個表示 JVM 系統(tǒng)屬性變量(通過 system.getproperties() 獲取),另一個代表系統(tǒng)環(huán)境變量(通過 system.getenv() 獲取)

這些默認的屬性源存在于 StandardEnvironmen 中,用于獨立應用程序。StandardServletEnvironment 中包含了額外的默認屬性源,包括 servlet配置servlet上下文參數(shù) 。同樣,StandardPortletEnvironment 也可以訪問 portlet 配置和portlet上下文參數(shù)作為屬性來源。兩者都可以選擇性地啟用JndiPropertySource。詳情請見javadocs

具體地說,在使用 StandardEnvironment 時,如果系統(tǒng)在運行時系統(tǒng)屬性變量或系統(tǒng)環(huán)境變量包含foo屬性,那么調用env.containsProperty("foo")將返回 true

屬性搜索是有層級結構的。默認情況下,系統(tǒng)屬性優(yōu)先于環(huán)境變量,所以如果foo屬性碰巧在對env.getproperty(“foo”)的調用中同時設置,系統(tǒng)屬性值將“勝出”,并優(yōu)先于環(huán)境變量返回。請注意,屬性值不會被合并,而是被前面的條目完全覆蓋。

對于一個常見的 StandardServletEnvironment,完整的層次結構如下所列,優(yōu)先級是由上而下的:

  • ServletConfig parameters (例如在 DispatcherServlet 上下文)
  • ServletContext parameters (web.xml 中 <context-param> 標簽)
  • JNDI environment variables ("java:comp/env/" 詞目)
  • JVM system properties ("-D" 命令行參數(shù))
  • JVM system environment (system 環(huán)境變量)

最重要的是,整個機制是可配置的。也許你有一個定制的屬性源,希望將其集成到這個搜索中。這個是沒有問題的——簡單地實現(xiàn)并實例化自己的 PropertySource,并將其添加到當前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注解提供了一種方便的聲明機制將 PropertySource 添加到 Spring 的 Environment中。

給定一個包含鍵/值對testbean.name=myTestBean的文件app.properties.下面的@configuration類使用@propertysourceapp.properties加載到 Spring 上下文中的Environment里面。然后調用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;
    }
}

假設my.placeholder存在于已經(jīng)注冊的一個屬性源中,例如系統(tǒng)屬性或者系統(tǒng)環(huán)境變量,占位符將被解析為相應的值。如果沒有,那么default/path將被用作默認值。如果沒有指定默認值,并且解析不到 property,則會拋出一個IllegalArgumentException 異常。

在Java 8特性中 @propertysource 注釋是可重復的。然而,所有這些 @propertysource 注解都需要在相同的級別上聲明:要么直接在配置類上,要么在同一個定制注解中作為元注解。直接注釋和元注釋的混合是不推薦的,因為直接注釋將覆蓋元注解。

4、Placeholder resolution in statements

在以前,elements 中的占位符的值只能通過JVM系統(tǒng)屬性或環(huán)境變量來解決。現(xiàn)在不再是這種情況了。因為環(huán)境抽象是集成在整個容器中的,所以很容易通過它來解決占位符的解析。這意味著您可以以任何您喜歡的方式配置決議過程:更改通過系統(tǒng)屬性和環(huán)境變量進行搜索的優(yōu)先級,或者完全刪除它們;在適當?shù)臅r候添加您自己的屬性源。

具體地說,不管用戶屬性的定義是什么,只要在環(huán)境中可用,下面的語句就可以工作:

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>

原文地址:

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容