(17)環(huán)境的抽象

本章將描述一下Spring中針對環(huán)境的抽象。

Environment是一個集成到容器之中的特殊抽象蚓峦,它針對應(yīng)用的環(huán)境建立了兩個關(guān)鍵的概念:profileproperties.

profile是命名好的俯逾,其中包含了多個Bean的定義的一個邏輯集合匾浪,只有當(dāng)指定的profile被激活的時候刺桃,其中的Bean才會激活草慧。無論是通過XML定義的還是通過注解解析的Bean都可以配置到profile之中桶蛔。而Environment對象的角色就是跟profile相關(guān)聯(lián),然后決定來激活哪一個profile漫谷,還有哪一個profile為默認(rèn)的profile仔雷。

properties在幾乎所有的應(yīng)用當(dāng)中都有著重要的作用,當(dāng)然也可能導(dǎo)致多個數(shù)據(jù)源:property文件,JVM系統(tǒng)property碟婆,系統(tǒng)環(huán)境變量电抚,JNDI,servlet上下文參數(shù)脑融,ad-hoc屬性對象,Map等缩宜。Environment對象和property相關(guān)聯(lián)肘迎,然后來給開發(fā)者一個方便的服務(wù)接口來配置這些數(shù)據(jù)源,并正確解析锻煌。

1.Bean定義的profile

在容器之中妓布,Bean定義profile是一種允許不同環(huán)境注冊不同bean的機(jī)制。環(huán)境的概念就意味著不同的東西對應(yīng)不同的開發(fā)者宋梧,而且這個特性能夠在一下的一些場景很有效:

  • 解決一些內(nèi)存中的數(shù)據(jù)源的問題匣沼,可以在不同環(huán)境訪問不同的數(shù)據(jù)源,開發(fā)環(huán)境捂龄,QA測試環(huán)境释涛,生產(chǎn)環(huán)境等。
  • 僅僅在開發(fā)環(huán)境來使用一些監(jiān)視服務(wù)
  • 在不同的環(huán)境倦沧,使用不同的bean實現(xiàn)
  • @profile注解

例子:模擬兩套環(huán)境唇撬,一個生產(chǎn)環(huán)境,一個是產(chǎn)品的環(huán)境展融,可以通過激活特定的環(huán)境窖认。來動態(tài)使用不同的環(huán)境

1)@profile注解在類中的使用
模擬生產(chǎn)環(huán)境

/**
 * @Project: spring
 * @description:  模擬生產(chǎn)環(huán)境
 * @author: sunkang
 * @create: 2018-09-21 09:17
 * @ModificationHistory who      when       What
 **/
@Configuration
@Profile("production")
public class ProductionDataConfig {

    public String dataSource() throws Exception {
        return "我是生產(chǎn)環(huán)境";
    }
}

模擬開發(fā)環(huán)境

/**
 * @Project: spring
 * @description:   模擬開發(fā)環(huán)境
 * @author: sunkang
 * @create: 2018-09-21 09:15
 * @ModificationHistory who      when       What
 **/
@Configuration
@Profile("dev")
public class DevDataConfig {
    @Bean
    public String  dataSource() {
        return "我是開發(fā)環(huán)境";
    }
}

2)@Profile注解可以當(dāng)做元注解來使用。比如告希,下面所定義的@Production注解就可以來替代@Profile("production")

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

3)@Profile注解也可以在方法級別使用扑浸,可以聲明在包含@Bean注解的方法之上:

/**
 * @Project: spring
 * @description:  @Profile在注解上的使用
 * @author: sunkang
 * @create: 2018-09-21 09:04
 * @ModificationHistory who      when       What
 **/
@Configuration
public class DatasourceConfig {
    @Bean
    @Profile("dev")
    public String  devDataSource() {
        return "我是開發(fā)環(huán)境";
    }
    @Profile("production")
    public String productionDataSource() throws Exception {
        return "我是生產(chǎn)環(huán)境";
    }
}

如果配置了@Configuration的類同時配置了@Profile,那么所有的配置了@Bean注解的方法和@Import注解的相關(guān)的類都會被傳遞為該P(yáng)rofile除非這個Profile激活了燕偶,否則Bean定義都不會激活喝噪。如果配置為@Component或者@Configuration的類標(biāo)記了@Profile({"p1", "p2"}),那么這個類當(dāng)且僅當(dāng)Profile是p1或者p2的時候才會激活。如果某個Profile的前綴是!這個否操作符指么,那么@Profile注解的類會只有當(dāng)前的Profile沒有激活的時候才能生效仙逻。舉例來說,如果配置為@Profile({"p1", "!p2"})涧尿,那么注冊的行為會在Profile為p1或者是Profile為非p2的時候才會激活系奉。

  • XML中Bean定義的profile

在XML中相對應(yīng)配置是<beans/>中的profile屬性。我們在前面配置的信息可以被重寫到XML文件之中如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans  profile="dev"
        xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>

    <bean  id="devDataConfig" class="java.lang.String">
        <constructor-arg index="0"  value="我是開發(fā)環(huán)境"/>
    </bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans  profile="production"
        xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
    <bean  id="devDataConfig" class="java.lang.String">
        <constructor-arg index="0"  value="我是開發(fā)環(huán)境"/>
    </bean>
</beans>

當(dāng)然姑廉,也可以通過嵌套<beans/>標(biāo)簽來完成定義部分:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>

    <beans profile="dev">
        <bean  id="dev" class="java.lang.String">
            <constructor-arg index="0"  value="我是開發(fā)環(huán)境"/>
        </bean>
    </beans>

    <beans profile="production">
        <bean   id="production"   class="java.lang.String">
            <constructor-arg index="0"  value="我是生產(chǎn)環(huán)境"/>
        </bean>
    </beans>

</beans>
  • 激活profile

現(xiàn)在缺亮,我們已經(jīng)更新了配置信息來使用環(huán)境抽象,但是我們還需要告訴Spring來激活具體哪一個Profile。如果我們直接啟動應(yīng)用的話萌踱,現(xiàn)在就回拋出NoSuchBeanDefinitionException異常葵礼,因為容器會找不到Spring的BeandataSource。

有多種方法來激活一個Profile并鸵,最直接的方式就是通過編程的方式來直接調(diào)用EnvironmentAPI鸳粉,ApplicationContext中包含這個接口:

 AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().setActiveProfiles("dev");
        ctx.register(DevDataConfig.class,ProductionDataConfig.class);
        ctx.refresh();
        String dataSource=  ctx.getBean("dataSource",String.class);
        System.out.println(dataSource);

額外的,Profile還可以通過spring.profiles.active中的屬性來通過系統(tǒng)環(huán)境變量园担,JVM系統(tǒng)變量届谈,servlet上下文中的參數(shù),甚至是JNDI的一個參數(shù)等來寫入。在集成測試中弯汰,激活Profile可以通過spring-test中的@ActiveProfiles來實現(xiàn)艰山。

需要注意的是,Profile的定義并不是一種互斥的關(guān)系咏闪,我們完全可以在同一時間激活多個Profile的曙搬。編程上來說,為setActiveProfile()方法提供多個Profile的名字即可:

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().setActiveProfiles("dev","production");

也可以通過spring.profiles.active來指定鸽嫂,逗號分隔的多個Profile的名字:

-Dspring.profiles.active="profile1,profile2"
  • 默認(rèn)profile

@Configuration
@Profile("default")
public class DefalutDataConfig {
    @Bean
    public String  dataSource() {
        return "我是默認(rèn)環(huán)境";
    }
}

如果沒有其他的Profile被激活纵装,那么上面代碼定義的dataSource就會被創(chuàng)建,這種方式就是為默認(rèn)情況下提供Bean定義的一種方式据某。一旦任何一個Profile激活了搂擦,那么默認(rèn)的Profile就不會激活

默認(rèn)的Profile的名字可以通過Environment中的setDefaultProfiles()方法或者是通過spring.profiles.default屬性來更改哗脖。

2.屬性源抽象

Spring的Environment的抽象提供了一些搜索選項瀑踢,來層次化配置的源信息。具體的內(nèi)容才避,參考如下代碼:

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

在上面的代碼片段之中橱夭,我們看到一個high-level的查找Spring的foo屬性是否定義的一種方式。為了知道Spring中是否包含這個屬性桑逝,Environment對象會針對PropertySource的集合進(jìn)行查找棘劣。PropertySource是針對一些key-value的屬性對的簡單抽象而Spring的StandardEnvironment是由兩個PropertySource對象所組成的楞遏,一個代表的是JVM的系統(tǒng)屬性(可以通過System.getProperties()來獲炔缦尽),而另一種則是系統(tǒng)的環(huán)境變量(通過System.getenv()來獲取寡喝。

這些默認(rèn)的屬性源都是StandardEnvironment的代表糙俗,可以用在任何應(yīng)用之中。StandardServletEnvironment則是包含Servlet配置的環(huán)境信息预鬓,其中會包含很多Servlet的配置和Servlet上下文參數(shù)巧骚。StandardPortletEnvironment類似于StandardServletEnvironment,能夠配置portlet上下文參數(shù)。

具體的說劈彪,當(dāng)使用StandardEnvironment的時候竣蹦,調(diào)用env.containsProperty("foo")將返回一個foo的系統(tǒng)屬性,或者是foo的運(yùn)行時環(huán)境變量沧奴。

查詢配置屬性是按層次來查詢的痘括。默認(rèn)情況下,系統(tǒng)屬性優(yōu)優(yōu)于系統(tǒng)環(huán)境變量滔吠,所以如果foo屬性在兩個環(huán)境中都有配置的話纲菌,那么在調(diào)用env.getProperty("foo")期間,系統(tǒng)屬性值會優(yōu)先返回屠凶。需要注意的是驰后,屬性的值是不會合并的肆资,而是完全覆蓋掉矗愧。
在一個普通的StandardServletEnvironment之中,查找的順序如下郑原,優(yōu)先查找* ServletConfig參數(shù)(比如DispatcherServlet上下文)唉韭,然后是* ServletContext參數(shù)(web.xml中的上下文參數(shù)),再然后是* JNDI環(huán)境變量犯犁,JVM系統(tǒng)變量(”-D”命令行參數(shù))以及JVM環(huán)境變量(操作系統(tǒng)環(huán)境變量)属愤。

最重要的是,整個的機(jī)制是可以配置的酸役。也許開發(fā)者自己有些定義的配置源信息想集成到配置檢索的系統(tǒng)中去住诸。沒問題倘感,只要實現(xiàn)開發(fā)者自己的PropertySource并且將其加入到當(dāng)前Environment的PropertySources之中即可

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

在上面的代碼之中回懦,MyPropertySource被添加到檢索配置的第一優(yōu)先級之中如果存在一個foo屬性届惋,它將由于其他的PropertySource之中的foo屬性優(yōu)先返回入桂。MutablePropertySourcesAPI提供一些方法來允許精確控制配置源奄薇。

3.@PropertySource注解

@PropertySource注解提供了一種方便的機(jī)制來將PropertySource增加到Spring的Environment之中
配置文件app.properties在classpath的environment目錄下抗愁,具體配置如下:

defalutPath=environment
name=sun
age=21
addr=hangzhou

java的配置如下:

/**
 * @Project: spring
 * @description:  @PropertySource讀取屬性配置文件
 * @author: sunkang
 * @create: 2018-09-22 22:33
 * @ModificationHistory who      when       What
 **/
@Configuration
@PropertySource("classpath:/environment/app.properties")
public class AppConfig {
    @Autowired
    Environment environment;
    @Bean
    public String nameValue(){
        return environment.getProperty("name");
    }
    @Bean
    public String ageValue(){
        return environment.getProperty("age");
    }
    @Bean
    public String addrValue(){
        return environment.getProperty("addr");
    }
}

測試如下:

/**
 * @Project: spring
 * @description:  @PropertySource的測試如下
 * @author: sunkang
 * @create: 2018-09-22 22:37
 * @ModificationHistory who      when       What
 **/
public class TestBean {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext();
        context.register(AppConfig.class);
        context.refresh();
        String value = (String) context.getBean("nameValue");
        System.out.println(value);
    }
}

測試結(jié)果:

sun

任何的@PropertySource之中形如${...}的占位符馁蒂,都可以被解析成Environment中的屬性資源,比如:

/**
 * @Project: spring
 * @description:   用了占位符蜘腌,但是占位符的屬性沫屡,要提前注冊
 * @author: sunkang
 * @create: 2018-09-22 22:33
 * @ModificationHistory who      when       What
 **/
@Configuration
@PropertySource("classpath:/${placeHolder.environment:environment}/app.properties")
public class AppConfig {

    @Autowired
    Environment environment;
    @Bean
    public String nameValue(){
        return environment.getProperty("name");
    }
    @Bean
    public String ageValue(){
        return environment.getProperty("age");
    }
    @Bean
    public String addrValue(){
        return environment.getProperty("addr");
    }
}

假設(shè)上面的placeHolder.environment是我們已經(jīng)注冊到Environment之中的資源,舉例來說撮珠,JVM系統(tǒng)屬性或者是環(huán)境變量的話谁鳍,占位符會解析成對象的值。如果沒有的話,default/path會來作為默認(rèn)值倘潜。也就是environment,如果沒有指定默認(rèn)值绷柒,而且占位符也解析不出來的話,就會拋出IllegalArgumentException涮因。

測試類需要記性調(diào)整:

public class TestBean {
   public static void main(String[] args) {
       //比如先注冊一些屬性
       Properties properties= System.getProperties();
       properties.setProperty("placeHolder.environment","environment");
       AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext();
       context.register(AppConfig.class);
       context.refresh();
       String value = (String) context.getBean("nameValue");
       System.out.println(value);
   }
}

4.占位符解析

從歷史上來說废睦,占位符的值是只能針對JVM系統(tǒng)屬性或者環(huán)境變量來解析的。但是現(xiàn)在不是了养泡,因為環(huán)境抽象已經(jīng)繼承到了容器之中嗜湃,現(xiàn)在很容易將占位符解析。這意味著開發(fā)者可以任意的配置占位符:

  • 調(diào)整系統(tǒng)變量還有環(huán)境變量的優(yōu)先級
  • 增加自己的屬性源信息

具體的說澜掩,下面的XML配置不會在意customer屬性在哪里定義购披,只有這個值在Environment之中有效即可:

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

5. 注冊LoadTimeWeaver

Spring使用LoadTimeWeaver當(dāng)將類加載進(jìn)JVM時進(jìn)行動態(tài)轉(zhuǎn)換。
為了使得加載時間織入肩榕,在你的@Configuration類上加入@EnableLoadTimeWeaving刚陡。

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

或者是使用元素context:load-time-weaver

<beans>
    <context:load-time-weaver/>
</beans>

只要配置了ApplicationContext。ApplicationContext中的任何Bean可能實現(xiàn)LoadTimeWearveAware株汉,從而接受load-time weaver 實例的引用筐乳。與Spring 的JPA一起使用是很有用的。這里load-time weaving對于 Spring’s JPA support 類轉(zhuǎn)換是很必要的乔妈。詳情參考Load-time weaving with AspectJ in the Spring Framework

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蝙云,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子路召,更是在濱河造成了極大的恐慌勃刨,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件股淡,死亡現(xiàn)場離奇詭異身隐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)揣非,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門抡医,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人早敬,你說我怎么就攤上這事忌傻。” “怎么了搞监?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵水孩,是天一觀的道長。 經(jīng)常有香客問我琐驴,道長俘种,這世上最難降的妖魔是什么秤标? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮宙刘,結(jié)果婚禮上苍姜,老公的妹妹穿的比我還像新娘。我一直安慰自己悬包,他們只是感情好衙猪,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著布近,像睡著了一般垫释。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撑瞧,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天棵譬,我揣著相機(jī)與錄音,去河邊找鬼预伺。 笑死订咸,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的扭屁。 我是一名探鬼主播算谈,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼涩禀,長吁一口氣:“原來是場噩夢啊……” “哼料滥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起艾船,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤葵腹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后屿岂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體践宴,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年爷怀,在試婚紗的時候發(fā)現(xiàn)自己被綠了阻肩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡运授,死狀恐怖烤惊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吁朦,我是刑警寧澤柒室,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站逗宜,受9級特大地震影響雄右,放射性物質(zhì)發(fā)生泄漏空骚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一擂仍、第九天 我趴在偏房一處隱蔽的房頂上張望囤屹。 院中可真熱鬧,春花似錦逢渔、人聲如沸牺丙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冲簿。三九已至,卻和暖如春亿昏,著一層夾襖步出監(jiān)牢的瞬間峦剔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工角钩, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留吝沫,地道東北人。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓递礼,卻偏偏與公主長得像惨险,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子脊髓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355

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