SpringBoot自動裝配原理

  • 本文參考的博客(基本是摘自博客中的內(nèi)容):
    http://www.reibang.com/p/83693d3d0a65
    http://www.reibang.com/p/10da93ef57d1
  • springboot一大優(yōu)勢就是省去了很多的配置瑞你,也就是說當(dāng)springboot啟動的時(shí)候继找,springboot在內(nèi)部就已經(jīng)幫忙封裝好了齿穗,者其實(shí)就是springboot的自動裝配操作耀怜。

1告嘲、從@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 {

    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
//根據(jù)包路徑掃描
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
//直接根據(jù)class類掃描
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

}

初看@SpringBootApplication有很多的注解組成撕彤,其實(shí)歸納就是一個(gè)"三體"結(jié)構(gòu)配椭,重要的只有三個(gè)Annotation:

@Configuration(@SpringBootConfiguration實(shí)質(zhì)就是一個(gè)@Configuration)
@EnableAutoConfiguration
@ComponentScan

  • 也就是說我們在開發(fā)的時(shí)候匿乃,加上上面的上個(gè)注解會等同于加上@SpringBootApplication注解

(1)@Configuration注解

  • 這個(gè)注解實(shí)際上就是代表了一個(gè)配置類,相當(dāng)于一個(gè)beans.xml文件初橘,可以看我的另一篇文章:
    http://www.reibang.com/p/81880251a700

(2)@ComponentScan

  • @ComponentScan的功能其實(shí)就是自動掃描并加載符合條件的組件或bean定義验游,最終將這些bean定義加載到容器中,也可以看我的另一篇文章:
    http://www.reibang.com/p/a00d2b43e160

(3)@EnableAutoConfiguration

  • 在spring中有關(guān)于@Enablexxx的注解是開啟某一項(xiàng)功能的注解保檐,比如@EnableScheduling表示開啟spring的定時(shí)任務(wù)耕蝉。其原理是借助@Import的幫助,將所有符合自動配置條件的bean定義加載到Ioc容器夜只。其有關(guān)@Import注解的原理可有看:http://www.reibang.com/p/a00d2b43e160
  • EnableAutoConfiguration代表開啟springboot的自動裝配
@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 {};
}

從源碼中可以知道垒在,最關(guān)鍵的要屬@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector扔亥,@EnableAutoConfiguration可以幫助SpringBoot應(yīng)用將所有符合條件的@Configuration配置都加載到當(dāng)前SpringBoot創(chuàng)建并使用的IoC容器场躯。同時(shí)借助于Spring框架原有的一個(gè)工具類:SpringFactoriesLoader,@EnableAutoConfiguration就可以實(shí)現(xiàn)智能的自動配置旅挤。

//從這里可以看出該類實(shí)現(xiàn)很多的xxxAware和DeferredImportSelector踢关,所有的aware都優(yōu)先于selectImports
//方法執(zhí)行,也就是說selectImports方法最后執(zhí)行粘茄,那么在它執(zhí)行的時(shí)候所有需要的資源都已經(jīng)獲取到了
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
//1加載META-INF/spring-autoconfigure-metadata.properties文件
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
//2獲取注解的屬性及其值(PS:注解指的是@EnableAutoConfiguration注解)
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//3.在classpath下所有的META-INF/spring.factories文件中查找org.springframework.boot.autoconfigure.EnableAutoConfiguration的值耘成,并將其封裝到一個(gè)List中返回
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//4.對上一步返回的List中的元素去重、排序
            configurations = this.removeDuplicates(configurations);
//5.依據(jù)第2步中獲取的屬性值排除一些特定的類
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
//6對上一步中所得到的List進(jìn)行過濾,過濾的依據(jù)是條件匹配瘪菌。這里用到的過濾器是
//org.springframework.boot.autoconfigure.condition.OnClassCondition最終返回的是一個(gè)ConditionOutcome[]
//數(shù)組撒会。(PS:很多類都是依賴于其它的類的,當(dāng)有某個(gè)類時(shí)才會裝配师妙,所以這次過濾的就是根據(jù)是否有某個(gè)
//class進(jìn)而決定是否裝配的诵肛。這些類所依賴的類都寫在META-INF/spring-autoconfigure-metadata.properties文件里)
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    }
  protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

...
}
  • SpringFactoriesLoader中加載配置,SpringFactoriesLoader屬于Spring框架私有的一種擴(kuò)展方案,其主要功能就是從指定的配置文件META-INF/spring.factories加載配置,即根據(jù)@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查找的Key,獲取對應(yīng)的一組@Configuration類
public abstract class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null)
            return result;
        try {
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    List<String> factoryClassNames = Arrays.asList(
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                    result.addAll((String) entry.getKey(), factoryClassNames);
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
...

總結(jié):@EnableAutoConfiguration作用就是從classpath中搜尋所有的META-INF/spring.factories配置文件默穴,并將其中org.springframework.boot.autoconfigure.EnableutoConfiguration對應(yīng)的配置項(xiàng)通過反射(Java Refletion)實(shí)例化為對應(yīng)的標(biāo)注了@Configuration的JavaConfig形式的IoC容器配置類怔檩,然后匯總為一個(gè)并加載到IoC容器。這些功能配置類要生效的話蓄诽,會去classpath中找是否有該類的依賴類(也就是pom.xml必須有對應(yīng)功能的jar包才行)并且配置類里面注入了默認(rèn)屬性值類薛训,功能類可以引用并賦默認(rèn)值。生成功能類的原則是自定義優(yōu)先仑氛,沒有自定義時(shí)才會使用自動裝配類乙埃。

  • 所以功能類能生效需要的條件:(1)spring.factories里面有這個(gè)類的配置類(一個(gè)配置類可以創(chuàng)建多個(gè)圍繞該功能的依賴類)(2)pom.xml里面需要有對應(yīng)的jar包

二、自動裝配案例說明以Redis為例

1锯岖、從spring-boot-autoconfigure.jar/META-INF/spring.factories中獲取redis的相關(guān)配置類全限定名(有120多個(gè)的配置類)RedisAutoConfiguration介袜,一般一個(gè)功能配置類圍繞該功能,負(fù)責(zé)管理創(chuàng)建多個(gè)相關(guān)的功能類出吹,比如RedisAutoConfiguration負(fù)責(zé):JedisConnectionFactory遇伞、RedisTemplate、StringRedisTemplate這3個(gè)功能類的創(chuàng)建


spring.factories中的redis配置類

2捶牢、RedisAutoConfiguration配置類生效的一個(gè)條件是在classpath路徑下有RedisOperations類存在鸠珠,因此springboot的自動裝配機(jī)制會會去classpath下去查找對應(yīng)的class文件。

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
...}

3.如果pom.xml有對應(yīng)的jar包,就能匹配到對應(yīng)依賴class秋麸,

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

4跳芳、匹配成功,這個(gè)功能配置類才會生效竹勉,同時(shí)會注入默認(rèn)的屬性配置類@EnableConfigurationProperties(RedisProperties.class)

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
    private int database = 0;
    private String url;
    private String host = "localhost";
    private String password;
    private int port = 6379;
...

5.Redis功能配置里面會根據(jù)條件生成最終的JedisConnectionFactory、RedisTemplate,并提供了默認(rèn)的配置形式@ConditionalOnMissingBean(name = "redisTemplate")

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

    @Bean
//用戶沒定義就使用默認(rèn)的
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

}

6.最終創(chuàng)建好的默認(rèn)裝配類娄琉,會通過功能配置類里面的 @Bean注解次乓,注入到IOC當(dāng)中
7.用戶使用,當(dāng)用戶在配置文件中自定義時(shí)候就會覆蓋默認(rèn)的配置@ConditionalOnMissingBean(name = "redisTemplate")

三孽水、自動依賴過程總結(jié)

1.通過各種注解實(shí)現(xiàn)了類與類之間的依賴關(guān)系票腰,容器在啟動的時(shí)候Application.run,會調(diào)用EnableAutoConfigurationImportSelector.class的selectImports方法(其實(shí)是其父類的方法)--這里需要注意女气,調(diào)用這個(gè)方法之前發(fā)生了什么和是在哪里調(diào)用這個(gè)方法需要進(jìn)一步的探討

2.selectImports方法最終會調(diào)用SpringFactoriesLoader.loadFactoryNames方法來獲取一個(gè)全面的常用BeanConfiguration列表

3.loadFactoryNames方法會讀取FACTORIES_RESOURCE_LOCATION(也就是spring-boot-autoconfigure.jar 下面的spring.factories)杏慰,獲取到所有的Spring相關(guān)的Bean的全限定名ClassName,大概120多個(gè)

4.selectImports方法繼續(xù)調(diào)用filter(configurations, autoConfigurationMetadata);這個(gè)時(shí)候會根據(jù)這些BeanConfiguration里面的條件,來一一篩選缘滥,最關(guān)鍵的是
@ConditionalOnClass轰胁,這個(gè)條件注解會去classpath下查找,jar包里面是否有這個(gè)條件依賴類朝扼,所以必須有了相應(yīng)的jar包赃阀,才有這些依賴類,才會生成IOC環(huán)境需要的一些默認(rèn)配置Bean

5.最后把符合條件的BeanConfiguration注入默認(rèn)的EnableConfigurationPropertie類里面的屬性值擎颖,并且注入到IOC環(huán)境當(dāng)中

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末榛斯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子搂捧,更是在濱河造成了極大的恐慌驮俗,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件允跑,死亡現(xiàn)場離奇詭異王凑,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吮蛹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門荤崇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人潮针,你說我怎么就攤上這事术荤。” “怎么了每篷?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵瓣戚,是天一觀的道長。 經(jīng)常有香客問我焦读,道長子库,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任矗晃,我火速辦了婚禮仑嗅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘张症。我一直安慰自己仓技,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布俗他。 她就那樣靜靜地躺著脖捻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪兆衅。 梳的紋絲不亂的頭發(fā)上地沮,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天嗜浮,我揣著相機(jī)與錄音,去河邊找鬼摩疑。 笑死危融,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的未荒。 我是一名探鬼主播专挪,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼片排!你這毒婦竟也來了寨腔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤率寡,失蹤者是張志新(化名)和其女友劉穎迫卢,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冶共,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乾蛤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捅僵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片家卖。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖庙楚,靈堂內(nèi)的尸體忽然破棺而出上荡,到底是詐尸還是另有隱情,我是刑警寧澤馒闷,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布酪捡,位于F島的核電站,受9級特大地震影響纳账,放射性物質(zhì)發(fā)生泄漏逛薇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一疏虫、第九天 我趴在偏房一處隱蔽的房頂上張望永罚。 院中可真熱鬧,春花似錦卧秘、人聲如沸呢袱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至醇锚,卻和暖如春哼御,著一層夾襖步出監(jiān)牢的瞬間坯临,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工恋昼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留看靠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓液肌,卻偏偏與公主長得像挟炬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子嗦哆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

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

  • 傳統(tǒng)的Spring項(xiàng)目會有很多的配置文件谤祖,比如我們要使用Redis,一般除了對應(yīng)的依賴的jar包我們還需要在app...
    黃靠譜閱讀 4,410評論 0 6
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,803評論 6 342
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理老速,服務(wù)發(fā)現(xiàn)粥喜,斷路器,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 個(gè)人專題目錄[http://www.reibang.com/u/2a55010e3a04] 一橘券、Spring B...
    Java及SpringBoot閱讀 2,827評論 1 25
  • 鏈接:http://www.cnblogs.com/xiaoxi/作者:平凡希 我們開發(fā)任何一個(gè)Spring Bo...
    聆世冷暖閱讀 4,118評論 1 35