Environment abstraction

##Environment abstraction (環境抽象) Environment是容器重要的抽象,它集成了應用兩個重要的方面:profiles和properties;java

一個Profile是一組已命名的有邏輯的bean定義,只有當特定的profile啓動時,它們纔在容器裏註冊.Beans能夠經過xml或者註解來指定其profile值.Environment對象在profiles中的角色決定目前哪一個profile會被啓動,哪一個profile是默認啓動.web

Properties在全部的應用中都起重要做用,它能夠從如下資源中產生:properties文件,JVM系統屬性,系統環境變量,JNDI,servlet上下文參數,特定的Properties對象,Maps,等.Environment對象與屬性的關係是提供使用者一個合適的服務接口來配置屬性資源,並從中釋放屬性. ###7.13.1 Bean definition profiles bean定義中的Profilesspring

bean定義的profiles是一個核心容器的機制,容許在不一樣的環境裏註冊不一樣的bean.這個單詞"environment"對不一樣的用戶表明不一樣的事物,這個功能在不少場景下給你提供幫助,包括:sql

  • 開發中使用內存數據庫 VS 在QA或production中使用從JNDI中查找相同的數據源.
  • 在一個複雜的環境部署項目如何註冊監控組件.
  • 部署時,對消費者A或B註冊不一樣的beans的自定義實現

讓我討論第一個例子要求一個特定的數據源.在一個測試環境中,這個配置可能以下:數據庫

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}

如今讓咱們思考一下如何在一個QA或生產環境部署應用,說明這個應用的數據源將會註冊到生產應用服務器的JNDI目錄裏.咱們的數據源可能以下:編程

@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}

難點在於如何根據當前環境來使用這兩種Bean.長久以來,pring用戶已經有不少方式來實現它,通常而言是一大堆系統環境變量和包含了${placeholder}令牌的xml的 <import/>元素,這樣就能夠根據環境變量的值來釋放正確的配置文件路徑.Bean定義的profile是核心容器的功能,能夠爲該問題提供解決方案.服務器

若是咱們歸納上面的而特定環境bean定義的使用狀況,咱們最終須要在特定的上下文中使註冊特定的bean定義,而不是其餘的.你能夠說你要在A環境中註冊一個bean定義的profile,並在B環境中註冊另外一個profile.讓我看看如何更新咱們的配置來反應這個需求.app

####@Profile Profile註解容許你代表一個逐漸有資格在一個或多個特定的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");
    }
}

像之前提到的,你可使用編程的JNDI查找:可使用JndiTemplate/JndiLocatorDelegate 幫助者或直接使用上文中展現的JNDI InitialContext,但不要使用JndiObjectFactoryBean,由於它強制要求你將返回類型設置爲FactoryBean.ui

@Profile能夠在建立一個自定義組合註解時做爲元註解使用.下面的例子就是定義一個@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");
    }
}

若是一個@Configuration類標誌爲@Profile,這個類中的全部的@Bean方法和@Import註解都會被繞過除非一個或多個特定的profile啓動.若是一個@Component或@Configuration類被標誌爲@Profile({"p1","p2"}),那麼除非是'p1'或'p2'已經激活,不然這個類不會被註冊或處理.若是一個特定profile加上了非操做符(!),那麼只要這個特定的profile不激活,該註解下的bean都會被註冊.例如,特定的@Profile({"p1","p2"}),當profile'p1'激活或profile'p2'未激活時都會發生註冊.

###7.13.2 XML bean definition profiles

在xml部分中profile是<Beans>元素的屬性.上面的簡單配置能夠用如下xml文件重寫:

<beans profile="dev"
    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="dev">
        <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>元素時,是容許這麼構造的.這能夠在避免xml文件的混亂性並提升其靈活性; ####Activing a profile 啓動一個profille 如今咱們已經更新了配置,咱們只須要肯定spring中哪一個profile要啓動.若是咱們如今啓動咱們的實例應用,咱們會發現一個NoSuchBeanDefinitionException的異常拋出,由於容器沒法找到名爲dataSource的bean. 能夠用好幾種方法啓動一個profile,最直接的方式就是經過一個ApplicationContext獲得的Environment對象的API方法來動態實現:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

另外,profiles能夠經過spring.profiles.active屬性來申明式啓動,它能夠經過環境變量,JVM系統屬性,web.xml裏的servlet上下文參數,或者JNDI裏的一個鍵值.在集成測試裏,能夠經過@ActiveProfiles來顯示的設置一個spring-test模塊的激活的profile.

記住,配置文件不是一個是或不是的命題;它能夠一次啓動多個混合profiles.編程式的,簡單的向setActiveProfiles()方法提供混合profile的名稱,該方法能夠接受任意string值.

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

聲明式的,spring.profiles.active也能夠接受一個profile名稱的標點分割集合.

spring.profiles.active="profile1,profile2"

####Default profile 默認profile default profile表示這個profile會被默認啓動.思考以下:

@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被啓動,那麼上面那個dataSource會被建立.這被認爲是提供一個或多個bean的默認定義的方式.若是有任意一個profile是可用的,那麼默認的profile將不會被啓用.

默認profile的名字可使用setDefaultProfiles()來替換,或者直接使用spring.profiles.default屬性來申明.

##@7.13.3 PropertySource abstraction 屬性資源抽象 spring的環境抽象經過屬性資源的配置結構提供了搜索操做.要詳細解釋,思考如下例子:

ApplicationContext ctx=new GenericAppliactionContext();
Environment env =ctx.getEnvironment();
boolean containsFoo=env.containsProperty("foo");
System.out.println("does my environment contain the  'foo' property"+ containsFoo);

在上面的片斷中,咱們能夠看到向spring詢問當前環境是否認義foo屬性的更高明的方式.要回答這個問題,Environment對象在一堆PropertySource對象裏執行查詢.一個propertySource是全部鍵值對的簡單抽象,而且spring的StandardEnvironment是由兩個PropertySource對象決定的,一個是JVM系統屬性(如 System.getProperties()),另外一個是系統環境變量(例如 System.getenv()).

對StandardEnvironment展示的這些屬性資源,能夠用於單獨的應用中.StandardServletEnvironment能夠操做其餘額外的默認屬性資源,包括servlet配置和servlet上下文參數.StandardPortletEnvironment一樣能夠向訪問配置資源同樣訪問portlet配置和portlet上下文參數.兩者均可以訪問JndiPropertySource.查看文檔細節.

具體而言,當使用StandardEnvironment,若是一個foo的系統屬性或foo 的 環境變量在運行時存在那麼調用env.containsProperty("foo")將返回true;

這個搜索動做有層次性.通常,系統屬性的優先級高於環境變量,因此當foo屬性在兩者都已設置且調用env.getProperty("foo")方法時,這個系統變量值將會獲勝並在返回是覆蓋掉環境變量.記住屬性值不會被合併,只會更加優先級來重寫.

對於一個普通的StandardServletEnvironment來講,如下全部的層次都會查找,高優先級的實體在上面:

  1. ServletConfig parameters (if applicable,e.g. in case of a DispatcherServlet context)
  2. ServletContext parameters(web.xml context-param entries)
  3. JNDI environment variables("java:comp/env/" entries)
  4. JVM system properties ("-D" command-line arguments)
  5. JVM system environment(operating system environment varibales)

最重要的是,整個機制是可配置的.或許你有個自定義屬性資源,你打算集成到搜索中.沒有任何問題,簡單的實現並實例化你本身的PropertySource,並將它加入當前Environment的PropertySource集合中.

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

在上面的代碼中,MyPropertySource被加入爲搜索中的更高優先級.若是他含有一個foo屬性,它將會被檢測並在其餘PropertySource以前返回.這個MutablePropertySource API暴露了大量的方法容許對屬性資源進行操做.

###7.13.4 @PropertySource 屬性資源

屬性資源註解提供了一個適合的,顯示的機制,它能夠吧一個PropertySource添加到spring的Environment中.

給定一個文件"app.properties"包含了一個鍵值對testbean.name=myTestBean,接下來的@Configuration類將這樣使用@PropertySource ,調用testBean.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中資源位置裏的${}提示符都會在環境中已註冊的屬性資源中進行翻譯.例如:

@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"在一個或多個已註冊的屬性資源中是已存在的,如系統屬性或環境變量,這個佔位符將轉化爲對應的值.若是沒有該值,那麼"default/path"將會用作一個默認值.若是沒有指定的默認值或屬性沒被轉化,那麼將拋出 IllegalArgumentException的異常.

###7.13.5 申明中的佔位符解決方案 曾經,在元素中的佔位符的值只能經過JVM系統屬性或環境變量轉化.如今不一樣了.由於環境抽象已經整合到容器裏,這很容易經過它來解決佔位符.這意味着你能夠以你喜歡的方式配置解決方案:經過系統環境和環境變量的搜索優先級修改或者移除他們;向混合資源適當的添加你本身的屬性資源;

具體的,只要customer在Environment中能夠得到,那麼下面的在申明使用了customer屬性的地方固然就會工做.

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>
相關文章
相關標籤/搜索