深入SpringBoot:自定義Conditional

前言

上一篇文章介紹了SpringBootEndpoint食绿,這里再介紹下@Conditional
SpringBoot的AutoConfig內(nèi)部大量使用了@Conditional钞速,會(huì)根據(jù)運(yùn)行環(huán)境來動(dòng)態(tài)注入Bean。這里介紹一些@Conditional的使用和原理,并自定義@Conditional來自定義功能叫乌。

Conditional

@ConditionalSpringFramework的功能踩衩,SpringBoot在它的基礎(chǔ)上定義了@ConditionalOnClass嚼鹉,@ConditionalOnProperty的一系列的注解來實(shí)現(xiàn)更豐富的內(nèi)容。
觀察@ConditionalOnClass會(huì)發(fā)現(xiàn)它注解了@Conditional(OnClassCondition.class)驱富。

    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(OnClassCondition.class)
    public @interface ConditionalOnClass {
        Class<?>[] value() default {};
        String[] name() default {};
    }

OnClassCondition則繼承了SpringBootCondition锚赤,實(shí)現(xiàn)了Condition接口。

    public interface Condition {
        boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
    }

查看SpringFramework的源碼會(huì)發(fā)現(xiàn)加載使用這些注解的入口在ConfigurationClassPostProcessor中褐鸥,這個(gè)實(shí)現(xiàn)了BeanFactoryPostProcessor接口线脚,前面介紹過,會(huì)嵌入到Spring的加載過程叫榕。
這個(gè)類主要是從ApplicationContext中取出Configuration注解的類并解析其中的注解浑侥,包括 @Conditional@Import@Bean等晰绎。
解析 @Conditional 邏輯在ConfigurationClassParser類中寓落,這里面用到了 ConditionEvaluator 這個(gè)類。

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            return;
        }
        ......
    }

ConditionEvaluator中的shouldSkip方法則使用了 @Conditional中設(shè)置的Condition類寒匙。

    public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
        if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
            return false;
        }
        if (phase == null) {
            if (metadata instanceof AnnotationMetadata &&
                    ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
                return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
            }
            return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
        }
        List<Condition> conditions = new ArrayList<Condition>();
        for (String[] conditionClasses : getConditionClasses(metadata)) {
            for (String conditionClass : conditionClasses) {
                Condition condition = getCondition(conditionClass, this.context.getClassLoader());
                conditions.add(condition);
            }
        }
        AnnotationAwareOrderComparator.sort(conditions);
        for (Condition condition : conditions) {
            ConfigurationPhase requiredPhase = null;
            if (condition instanceof ConfigurationCondition) {
                requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
            }
            if (requiredPhase == null || requiredPhase == phase) {
                if (!condition.matches(this.context, metadata)) {
                    return true;
                }
            }
        }
        return false;
    }
    private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
            MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(Conditional.class.getName(), true);
            Object values = (attributes != null ? attributes.get("value") : null);
            return (List<String[]>) (values != null ? values : Collections.emptyList());
    }

自定義Conditional

所以自定義Conditional就是通過自定義注解和Condition的實(shí)現(xiàn)類零如。完整的代碼在Github上了

  1. 定義@ConditionalOnMyProperties
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(OnMyPropertiesCondition.class)
    public @interface ConditionalOnMyProperties {
        String name();
    }
  1. 定義OnMyPropertiesCondition躏将,這里繼承了SpringBootCondition重用了部分功能,然后再getMatchOutcome實(shí)現(xiàn)了自定義的功能考蕾。
    public class OnMyPropertiesCondition extends SpringBootCondition {
        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Object propertiesName = metadata.getAnnotationAttributes(ConditionalOnMyProperties.class.getName()).get("name");
            if (propertiesName != null) {
                String value = context.getEnvironment().getProperty(propertiesName.toString());
                if (value != null) {
                    return new ConditionOutcome(true, "get properties");
                }
            }
            return new ConditionOutcome(false, "none get properties");
        }
    }
  1. ConditionalOnMyProperties使用類祸憋,還要加上Configuration注解才能生效。
    @Configuration
    @ConditionalOnMyProperties(name = "message")
    public static class ConditionClass {
        @Bean
        public HelloWorld helloWorld() {
            return new HelloWorld();
        }
    }
    private static class HelloWorld {
        public void print() {
            System.out.println("hello world");
        }
    }
  1. 入口類肖卧,這里運(yùn)行兩次SpringApplication蚯窥,傳入的參數(shù)不同,第一次取Bean會(huì)拋出Bean不存在的異常塞帐,第二次就會(huì)正常輸出拦赠。
    @Configuration
    @EnableAutoConfiguration
    public class CustomizeConditional {
        public static void main(String[] args) {
            SpringApplication springApplication = new SpringApplication(CustomizeConditional.class);
            springApplication.setWebEnvironment(false);
            ConfigurableApplicationContext noneMessageConfigurableApplicationContext = springApplication.run("--logging.level.root=ERROR","--endpoints.enabled=false");
            try {
                noneMessageConfigurableApplicationContext.getBean(HelloWorld.class).print();
            } catch (Exception e) {
                e.printStackTrace();
            }
            ConfigurableApplicationContext configurableApplicationContext = springApplication.run("--message=haha", "--logging.level.root=ERROR");
            configurableApplicationContext.getBean(HelloWorld.class).print();
        }
    }

結(jié)語

ConfigurationClassPostProcessor實(shí)現(xiàn)了很多的擴(kuò)展功能,很多額外的注解包括@Bean葵姥,@Import等都是由這個(gè)類解析的荷鼠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市榔幸,隨后出現(xiàn)的幾起案子允乐,更是在濱河造成了極大的恐慌,老刑警劉巖削咆,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牍疏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拨齐,警方通過查閱死者的電腦和手機(jī)鳞陨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瞻惋,“玉大人厦滤,你說我怎么就攤上這事∈焓罚” “怎么了馁害?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蹂匹。 經(jīng)常有香客問我碘菜,道長(zhǎng),這世上最難降的妖魔是什么限寞? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任忍啸,我火速辦了婚禮,結(jié)果婚禮上履植,老公的妹妹穿的比我還像新娘计雌。我一直安慰自己,他們只是感情好玫霎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布凿滤。 她就那樣靜靜地躺著妈橄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪翁脆。 梳的紋絲不亂的頭發(fā)上眷蚓,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音反番,去河邊找鬼沙热。 笑死,一個(gè)胖子當(dāng)著我的面吹牛罢缸,可吹牛的內(nèi)容都是我干的篙贸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼枫疆,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼爵川!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起养铸,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤雁芙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后钞螟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谎碍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年鳞滨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蟆淀。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拯啦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出熔任,到底是詐尸還是另有隱情褒链,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布疑苔,位于F島的核電站甫匹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏惦费。R本人自食惡果不足惜兵迅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望薪贫。 院中可真熱鬧恍箭,春花似錦、人聲如沸瞧省。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至交洗,卻和暖如春骑科,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背藕筋。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工纵散, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人隐圾。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓伍掀,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親暇藏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蜜笤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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