SpringBoot源碼解讀與原理分析(三)條件裝配

SpringBoot源碼解讀與原理分析(合集)

2.3 Spring Framework的條件裝配

在實際開發(fā)中我們可能遇到以下場景:測試環(huán)境用8080端口券盅,生產(chǎn)環(huán)境用9999端口;測試環(huán)境需要將某個組件注冊到IOC容器芝硬,但生產(chǎn)環(huán)境又不需要蛮拔。
為了解決在不同場景/條件/環(huán)境下滿足不同組件的裝配明场,Spring Framework提供了兩種條件裝配的方式:基于Profile和基于Conditional诅福。

2.3.1 基于Profile的裝配

1.Profile源碼解讀

If a {@code @Configuration} class is marked with {@code @Profile}, all of the {@code @Bean} methods and {@link Import @Import} annotations associated with that class will be bypassed unless one or more of the specified profiles are active.

如果一個標(biāo)注了@Configuration的配置類被標(biāo)注為@Profile朦拖,那么與該類關(guān)聯(lián)的所有@Bean方法和@Import}注釋將被繞過刃鳄,除非一個或多個指定的配置文件處于活動狀態(tài)古拴。

A profile is a named logical grouping that may be activated programmatically via {@link ConfigurableEnvironment#setActiveProfiles} or declaratively by setting the {@link AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME spring.profiles.active} property as a JVM system property, as an environment variable, or as a Servlet context parameter in {@code web.xml} for web applications.

這里描述激活Profile的三種方式:JVM啟動參數(shù)箩帚、環(huán)境變量、web.xml配置

簡單概括黄痪,Profile提供了一種“基于環(huán)境的配置”紧帕,根據(jù)當(dāng)前項目的不同運行時環(huán)境,可以動態(tài)地注冊與當(dāng)前運行環(huán)境匹配的組件桅打。

2.使用@Profile注解

(1)BartenderConfiguration類添加@Profile注解

public class Bartender {

    private String name;

    public Bartender(String name) {
        this.name = name;
    }

    // gettter setter
}
@Configuration
@Profile("city")
public class BartenderConfiguration {

    @Bean
    public Bartender zhangsan() {
        return new Bartender("張三");
    }

    @Bean
    public Bartender lisi() {
        return new Bartender("李四");
    }

}

(2)編程式配置Profile

public class TavernProfileApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BartenderConfiguration.class);
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
    }

}

執(zhí)行結(jié)果(已省略一些內(nèi)部組件打邮鞘取):

=========分割線=========
=========分割線=========

控制臺沒有打印zhangsan和lisi。

因為在默認(rèn)情況下挺尾,ApplicationContext中的Profile為“default”鹅搪,與配置的@Profile("city")不匹配,所以BartenderConfiguration不會生效遭铺,@Bean也就不會注冊到IOC容器中丽柿。

要想zhangsan和lisi注冊到IOC容器中恢准,則需要給ApplicationContext設(shè)置一下激活的Profile。

public class TavernProfileApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().setActiveProfiles("city");
        ctx.register(BartenderConfiguration.class);
        ctx.refresh();
        System.out.println("=========分割線=========");
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
        System.out.println("=========分割線=========");
    }

}

執(zhí)行結(jié)果(已省略一些內(nèi)部組件打痈μ狻):

=========分割線=========
bartenderConfiguration
zhangsan
lisi
=========分割線=========

zhangsan和lisi已注冊到IOC容器馁筐。

注意:這里AnnotationConfigApplicationContext在創(chuàng)建對象時,沒有傳入配置類坠非,則內(nèi)部不會執(zhí)行初始化邏輯敏沉,而是等到手動調(diào)用其refresh方法后才會初始化IOC容器(如果傳入了會立即初始化IOC容器),在初始化過程中炎码,一并處理環(huán)境配置盟迟。

(3)命令行參數(shù)配置Profile

上面使用的編程式配置Profile存在硬編碼問題,如果需要切換Profile潦闲,則需要修改代碼并重新編譯攒菠。為此,SpringFramework還支持命令行參數(shù)配置Profile矫钓。

在IDEA中配置啟動選項:


在main方法中改回原來的構(gòu)造方法傳入配置類的形式:

public class TavernProfileApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BartenderConfiguration.class);
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
    }

}

執(zhí)行結(jié)果(已省略一些內(nèi)部組件打印):

=========分割線=========
bartenderConfiguration
zhangsan
lisi
=========分割線=========

zhangsan和lisi成功注冊到IOC容器舍杜。

3.Profile運用于實際開發(fā)

application.properties文件可以通過加profile后綴來區(qū)分不同環(huán)境下的配置文件(application-dev.properties新娜、application-test.properties、application-prod.properties)

# application-dev.properties
server.port=8787

# application-prod.properties
server.port=8989

# application.properties
spring.profiles.active=dev #激活dev的配置

4.Profile的不足

Profile控制的是整個項目的運行環(huán)境既绩,無法根據(jù)單個Bean的因素決定是否裝配概龄。這種情況要用第二種條件裝配的方式:基于@Conditional注解。

2.3.2 基于Conditional的裝配

Conditional饲握,意為條件私杜,可以使Bean的裝配基于一些指定的條件。
換句話說救欧,被標(biāo)注@Conditional注解的Bean要注冊到IOC容器時衰粹,必須滿足@Conditional上指定的所有條件才允許注冊。

1.@Conditional源碼解讀

The {@code @Conditional} annotation may be used in any of the following ways:

  • as a type-level annotation on any class directly or indirectly annotated with {@code @Component}, including {@link Configuration @Configuration} classes
  • as a meta-annotation, for the purpose of composing custom stereotype annotations
  • as a method-level annotation on any {@link Bean @Bean} method

@Conditional的三種使用方式:

  • 在任何直接或間接用@Component標(biāo)注的類上作為類級別注笆怠,包括@Configuration類
  • 作為元注解铝耻,用于組合自定義構(gòu)造型注解
  • 作為任何@Bean方法上的方法級注解

If a {@code @Configuration} class is marked with {@code @Conditional}, all of the {@code @Bean} methods, {@link Import @Import} annotations, and {@link ComponentScan @ComponentScan} annotations associated with that class will be subject to the conditions.

如果一個@Configuration配置類標(biāo)注了@Conditional,那么與之相關(guān)聯(lián)的@Bean方法蹬刷,@Import導(dǎo)入瓢捉,@ComponentScan注解都將適用于這些條件。

Class<? extends Condition>[] value();

@Conditional注解需要傳入一個Condition接口實現(xiàn)類數(shù)組办成,說明在使用時還需要定義一個條件判斷類作為匹配依據(jù)泡态,實現(xiàn)Condition接口。

2.@Conditional使用

(1)創(chuàng)建判斷Boss是否存在的條件判斷類

public class Boss {
}
public class BossExistCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 使用BeanDefinition而不是Bean做判斷迂卢,這是因為考慮到:
        // 當(dāng)進行匹配時Boss對象可能尚未創(chuàng)建某弦,使用BeanDefinition
        // 可以確保不會出現(xiàn)偏差
        return context.getBeanFactory().containsBeanDefinition(Boss.class.getName());
    }
    
}

(2)吧臺配置類使用@Conditional桐汤,并傳入BossExistCondition

public class Bar {
}
@Configuration
public class BarConfiguration {

    @Bean
    @Conditional(BossExistCondition.class)
    public Bar bbBar() {
        return new Bar();
    }

}

(3)測試
@EnableTavern的內(nèi)容詳見:SpringBoot源碼解讀與原理分析(二)組件裝配
場景一:@EnableTavern只導(dǎo)入BarConfiguration,不導(dǎo)入Boss

@Documented
@Retention(RetentionPolicy.RUNTIME) //
@Target(ElementType.TYPE) // 該注解只能標(biāo)注到類上
@Import({BarConfiguration.class})
public @interface EnableTavern {

}

執(zhí)行結(jié)果(已省略一些內(nèi)部組件打拥堆隆):

=========分割線=========
tavernConfiguration
com.star.springboot.conditional.BarConfiguration
=========分割線=========

Boss和bbBar均沒有注冊到IOC容器中惊科。

場景二:@EnableTavern導(dǎo)入BarConfiguration和Boss

@Documented
@Retention(RetentionPolicy.RUNTIME) //
@Target(ElementType.TYPE) // 該注解只能標(biāo)注到類上
@Import({Boss.class, BarConfiguration.class})
public @interface EnableTavern {

}

執(zhí)行結(jié)果(已省略一些內(nèi)部組件打印):

=========分割線=========
tavernConfiguration
com.star.springboot.ioc.Boss
com.star.springboot.conditional.BarConfiguration
bbBar
=========分割線=========
Boss和bbBar均注冊到IOC容器中亮钦,說明@Conditional已經(jīng)起了作用馆截。

3.ConditionalOnXXX系列注解

SpringBoot針對@Conditional注解擴展了一系列條件注解。

  • @ConditionalOnClass & @ConditionalOnMissingClass :檢查當(dāng)前項目的類路徑下是否包含/缺少指定類蜂莉。
  • @ConditionalOnBean & @ConditionalOnMissingBean :檢查當(dāng)前容器中是否注冊/缺少指定Bean蜡娶。
  • @ConditionalOnProperty :檢查當(dāng)前應(yīng)用的屬性配置。
  • @ConditionalOnWebApplication & @ConditionalOnNotWebApplication :檢查當(dāng)前應(yīng)用是否為Web應(yīng)用映穗。
  • @ConditionalOnExpression :根據(jù)指定的SqEL表達式確定條件是否滿足窖张。

注意,@ConditionalOnXXX注解通常都用在自動配置類中蚁滋,對于普通的配置類最好避免使用宿接,以免出現(xiàn)判斷偏差。

SpringBoot源碼解讀與原理分析(合集)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辕录,一起剝皮案震驚了整個濱河市睦霎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌走诞,老刑警劉巖副女,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚣旱,居然都是意外死亡碑幅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門塞绿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沟涨,“玉大人,你說我怎么就攤上這事异吻】酱埽” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵涧黄,是天一觀的道長篮昧。 經(jīng)常有香客問我,道長笋妥,這世上最難降的妖魔是什么懊昨? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮春宣,結(jié)果婚禮上酵颁,老公的妹妹穿的比我還像新娘嫉你。我一直安慰自己,他們只是感情好躏惋,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布幽污。 她就那樣靜靜地躺著,像睡著了一般簿姨。 火紅的嫁衣襯著肌膚如雪距误。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天扁位,我揣著相機與錄音准潭,去河邊找鬼。 笑死域仇,一個胖子當(dāng)著我的面吹牛刑然,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播暇务,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼泼掠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了垦细?” 一聲冷哼從身側(cè)響起择镇,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝠检,沒想到半個月后沐鼠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挚瘟,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡叹谁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了乘盖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焰檩。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖订框,靈堂內(nèi)的尸體忽然破棺而出析苫,到底是詐尸還是另有隱情,我是刑警寧澤穿扳,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布衩侥,位于F島的核電站,受9級特大地震影響矛物,放射性物質(zhì)發(fā)生泄漏茫死。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一履羞、第九天 我趴在偏房一處隱蔽的房頂上張望峦萎。 院中可真熱鬧屡久,春花似錦、人聲如沸爱榔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽详幽。三九已至筛欢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間妒潭,已是汗流浹背悴能。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留雳灾,地道東北人漠酿。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像谎亩,于是被迫代替她去往敵國和親炒嘲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

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