spring-boot 自動裝配原理

SpringBoot自動配置原理
SpringBoot自動配置原理(SpringBoot自動裝配原理,SpringBoot starter原理)
SpringBoot可以根據(jù)定義在classpath下的類滨砍,自動的給你生成一些Bean妓肢,并加載到Spring的Context中街州,自動配置充分的利用了Spring 4.0的條件化配置特性,能夠自動配置特定的Spring bean,用來啟動某項特性告希;

關(guān)于條件化@Conditional注解:
如果你希望一個bean在某些條件下加載桥温,在某些條件下不加載引矩,則可以使用@Conditional注解;

@Configuration
public class MyConfig {
    @Bean
    @Conditional(MyBeanCondition.class)
    public MyBean myBean(){
        return new MyBean();
    }
}

public class MyBeanCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}

當(dāng)@Conditional(MyBeanCondition.class)為true時侵浸,MyBean才會被創(chuàng)建旺韭,否則不會創(chuàng)建;
有了條件注解以保證某些bean在沒滿足特定條件的情況下就可以不必初始化霹娄,避免在bean初始化過程中由于條件不足渠啊,導(dǎo)致應(yīng)用啟動失敗口叙。
Conditional:有條件的; 條件; 有條件; 條件響應(yīng); 條件式;

01织盼、ConditionalOnBean 當(dāng)spring容器中有某個bean時
02、ConditionalOnClass 當(dāng)有某個class時
03酱塔、ConditionalOnMissingBean 當(dāng)沒有某個bean時
04沥邻、ConditionalOnMissingClass
05、ConditionalOnCloudPlatform
06延旧、ConditionalOnExpression
07谋国、ConditionalOnJava
08、ConditionalOnJndi
09迁沫、ConditionalOnNotWebApplication
10芦瘾、ConditionalOnProperty
11、ConditionalOnResource
12集畅、ConditionalOnSingleCandidate
13近弟、ConditionalOnWebApplication
14、ConditionalOnRepositoryType
15挺智、ConditionalOnMissingFilterBean
16祷愉、ConditionalOnEnabledResourceChain

在編寫SpringBoot項目時,入口類上都會使用@SpringBootApplication注解了赦颇,我們可以看一下源代碼

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
          //這些是SpringBootApplication 注解的變量
          //exclude : 排除指定的類
          //@SpringBootApplication(exclude = RedisAutoConfiguration.class)可以排除自定義配置等等二鳄,根據(jù)自己的需要進(jìn)行定制。
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

        //excludeName:排除指定的類名媒怯,通過bean name來進(jìn)行排除指定的類订讼,如下:
      //@SpringBootApplication(excludeName = "org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration")
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

        //scanBasePackages: 掃描指定的包添加到Spring容器中,參數(shù)為數(shù)組類型:
 //@SpringBootApplication(scanBasePackages="com.bjpowernode.boot.component")
//掃描的包能注冊識別扇苞,沒有掃描的包將不能注冊識別欺殿;
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

        //scanBasePackageClasses: 掃描指定包的類并添加到Spring容器中寄纵,參數(shù)為Class類型數(shù)組格式:
//@SpringBootApplication(scanBasePackageClasses=MyComponent.class)
//注冊的類能識別,在同級包下或子包下的都能注冊脖苏,否則不能識別
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    boolean proxyBeanMethods() default true;

}

@SpringBootApplication注解本身又是一個復(fù)合注解程拭,它等效于如下四個注解:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
@ConfigurationPropertiesScan


image.png

@SpringBootConfiguration
該注解等效于@Configuration,那也就是說這個注解相當(dāng)于@Configuration棍潘,所以這兩個注解作用是一樣的恃鞋,它是讓我們能夠去注冊一些額外的Bean,或者導(dǎo)入一些額外的配置蜒谤,@Configuration還表示該類是一個配置類山宾,不需要額外的xml進(jìn)行配置,同時該類也是Spring容器中的一個bean鳍徽。

@EnableAutoConfiguration (重點)
該注解是Spring Boot自動配置注解资锰,Spring Boot中的自動配置主要是@EnableAutoConfiguration的功勞,該注解可以讓Spring Boot根據(jù)類路徑中的jar包依賴為當(dāng)前項目進(jìn)行自動配置.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};

}

該注解由如下兩個注解構(gòu)成:
1.@AutoConfigurationPackage
讓包中的類以及子包中的類能夠被自動掃描到Spring容器中阶祭;

2.@Import(EnableAutoConfigurationImportSelector.class)
這個注解就是通過import的方式將EnableAutoConfigurationImportSelector添加到Spring容器中绷杜;
EnableAutoConfigurationImportSelector 實現(xiàn)自動化配置導(dǎo)入

Spring框架本身也提供了幾個名字為@Enable開頭的Annotation定義。比如@EnableScheduling濒募、@EnableCaching等鞭盟,@EnableAutoConfiguration的理念和這些注解其實是一脈相承的。

@EnableScheduling是通過@Import將Spring調(diào)度框架相關(guān)的bean定義都加載到IoC容器瑰剃。

@EnableAutoConfiguration也是借助@Import的幫助齿诉,將所有符合自動配置條件的bean定義加載到IoC容器;


image.png

該注解通過@Import注解導(dǎo)入了一個組件:AutoConfigurationImportSelector晌姚,該組件實現(xiàn)了接口:

public interface DeferredImportSelector extends ImportSelector

該接口主要是為了導(dǎo)入@Configuration的配置項粤剧,而DeferredImportSelector是延期導(dǎo)入,當(dāng)所有的@Configuration都處理過后才會執(zhí)行挥唠;

導(dǎo)入AutoConfigurationImportSelector類,會執(zhí)行到它的process方法,如下:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...............................................................................
    private static class AutoConfigurationGroup
            implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
        @Override
        public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                    () -> String.format("Only %s implementations are supported, got %s",
                            AutoConfigurationImportSelector.class.getSimpleName(),
                            deferredImportSelector.getClass().getName()));
              //getAutoConfigurationMetadata() 獲取自動化配置的元數(shù)據(jù)
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
        }

      //獲取自動化配置元數(shù)據(jù)
      //AutoConfigurationMetadataLoader.loadMetadata  通過JDK Properties.load(inputStream) 來加載META-INF/spring-autoconfigure-metadata.properties中配置的需要自動加載的類,并最后封裝成PropertiesAutoConfigurationMetadata對象,將加載好的properties信息賦值其properties屬性
        private AutoConfigurationMetadata getAutoConfigurationMetadata() {
            if (this.autoConfigurationMetadata == null) {
                this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            }
            return this.autoConfigurationMetadata;
        }
    }
}

META-INF/spring-autoconfigure-metadata.properties中配置項,如下:
它的意思同@ConditionalOnClass注解,key為權(quán)限類名, value為key所依賴的其他Class,如果value不存在,則key所代表的類不會自動裝配


image.png

接下來((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry方法:

    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }

                //獲取注解@EnableAutoConfiguration的屬性,即exclude,excludeName
        AnnotationAttributes attributes = getAttributes(annotationMetadata);

                //加載文件中配置的自動化配置META-INF/spring.factories
                 // loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); 中g(shù)etOrDefault 取出org.springframework.boot.autoconfigure.EnableAutoConfiguration為key的value
                //Enumeration<URL> urls =classLoader.getResources("META-INF/spring.factories")
              //URL url = urls.nextElement();
        //UrlResource resource = new UrlResource(url);
                //InputStream is = resource.getInputStream();
                //Properties.load(is)
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

                //去重
        configurations = removeDuplicates(configurations);
                
                //獲取注解上的排除掉的類
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
                //刪除掉排除 項
        configurations.removeAll(exclusions);
                
                //過濾,過濾掉那些,還不滿足加載條件的類,即類的@Conditional的條件沒滿足,則不加載,去除掉
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

configurations = filter(configurations, autoConfigurationMetadata);的大概邏輯如下:

META-INF/spring.factories中配置的自動配置類(org.springframework.boot.autoconfigure.EnableAutoConfiguration的 value類),這些類會被作為自動裝配的候選類,接下來會根據(jù)META-INF/spring-autoconfigure-metadata.properties這個中配置的類與依賴類的對應(yīng)關(guān)系,去校驗該類所依賴的Class(ConditionalOnClass)是否存在(拿到className,通過Class.forName方法,不拋異常就說明該class存在),如果存在,則該類可以被自動裝配.
另外,這個地放比較耗時間,springboot將數(shù)據(jù)一分為二,并另起了一個線程,與主線程一起執(zhí)行校驗,如下代碼所示之處.

private ThreadedOutcomesResolver(OutcomesResolver outcomesResolver) {
            this.thread = new Thread(() -> this.outcomes = outcomesResolver.resolveOutcomes());
            this.thread.start();
        }

@EnableAutoConfiguration與@Conditional
@EnableAutoConfiguration自動加載配置抵恋,@Conditional根據(jù)環(huán)境決定是否解析處理配置,這兩個注解的配合完成了自動化配置功能宝磨;

在Spring Boot中怎么自定義自動配置弧关?
1、需要提供jar包唤锉,在jar包中需要包含META-INF/spring.factories文件世囊;
2、在spring.factories中添加配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration =
com.bjpowernode.xxxx.xxxConfiguration(該類我們自己去實現(xiàn)窿祥,里面主要是要做自動化的配置茸习,給使用者把該配的配置好,讓別人可以直接用)
3壁肋、xxxConfiguration的實現(xiàn)需要添加注解@Configuration号胚;
4、xxxConfiguration也可以選擇添加@Conditional來適應(yīng)不同的環(huán)境浸遗;
5猫胁、在xxxConfiguration類中實現(xiàn)自動化配置;
有了SpringBoot的自動化配置跛锌,我們可以靈活的自定義我們自己的自動配置弃秆,當(dāng)應(yīng)用需要該功能時,只需要簡單的依賴該jar包即可髓帽,同時Spring Boot為我們提供的條件注解菠赚,同樣的代碼可以靈活適應(yīng)各種環(huán)境;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末郑藏,一起剝皮案震驚了整個濱河市衡查,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌必盖,老刑警劉巖拌牲,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異歌粥,居然都是意外死亡塌忽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進(jìn)店門失驶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來土居,“玉大人,你說我怎么就攤上這事嬉探〔烈” “怎么了?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵甲馋,是天一觀的道長埂奈。 經(jīng)常有香客問我,道長定躏,這世上最難降的妖魔是什么账磺? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮痊远,結(jié)果婚禮上垮抗,老公的妹妹穿的比我還像新娘。我一直安慰自己碧聪,他們只是感情好冒版,可當(dāng)我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著逞姿,像睡著了一般辞嗡。 火紅的嫁衣襯著肌膚如雪捆等。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天续室,我揣著相機(jī)與錄音栋烤,去河邊找鬼。 笑死挺狰,一個胖子當(dāng)著我的面吹牛明郭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播丰泊,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼薯定,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瞳购?” 一聲冷哼從身側(cè)響起话侄,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎苛败,沒想到半個月后满葛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡罢屈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年嘀韧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缠捌。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡锄贷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出曼月,到底是詐尸還是另有隱情谊却,我是刑警寧澤,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布哑芹,位于F島的核電站炎辨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏聪姿。R本人自食惡果不足惜碴萧,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望末购。 院中可真熱鬧破喻,春花似錦、人聲如沸盟榴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至羽德,卻和暖如春几莽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玩般。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工银觅, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人坏为。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像镊绪,于是被迫代替她去往敵國和親匀伏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,446評論 2 359