Spring Boot自動裝配的原理

Spring Boot不得不說的一個特點就是自動裝配迅诬,它是starter的基礎(chǔ)董饰,也是spring boot的核心岳锁,那到底什么是自動裝配呢?

簡單的說吊输,就是自動將Bean裝配到IoC容器中饶号。接下來,我們通過一個例子來了解下自動裝配璧亚。

  1. 添加starter
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 在application.properties中添加Redis的配置
spring.redis.host=localhost
spring.redis.port=6379
  1. 在Controller中使用redisTemplate對Redis進(jìn)行操作
@RestController
public class RedisController {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping("/test")
    public String test() {
        redisTemplate.opsForValue().set("test", "test demo");
        return "Test Demo";
    }
}

在上面例子中讨韭,我們并沒有通過XML形式或者注解形式把RedisTemplate注入到IoC容器中,但是在RedisController中卻可以直接使用@Autowired來注入redisTemplate實例癣蟋,這就表明IoC容器中已經(jīng)存在RedisTemplate實例了透硝,這就是Spring Boot自動裝配機(jī)制。

自動裝配的實現(xiàn)

自動裝配在Spring boot中是通過@EnableAutoConfiguration注解來開啟的疯搅,這個注解是在啟動類注解@SpringBootApplication中聲明的濒生。

@SpringBootApplication
public class SpringBootDemoApplication {
    public static void main(String[] args) {
        SpringApplication,run(SpringBootDemoApplication.class, args);
    }
}

查看@SpringBootApplication注解,可以看到@EnableAutoConfiguration注解的聲明

@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{

其實spring 3.1版本就已經(jīng)開始支持@Enable注解了幔欧,它的主要作用是把相關(guān)組件的Bean裝配到IoC容器中罪治。@Enable注解對JavaConfig的進(jìn)一步完善,使開發(fā)者減少了配置代碼量礁蔗,降低了使用難度觉义,比較常見的Enable注解有@EnableWebMvc,@EnableScheduling等浴井。

如果我們要基于JavaConfig的形式來完成Bean的裝載晒骇,則必須要使用@注解及@Bean注解。@Enable注解本質(zhì)上就是對這兩種注解的封裝,在@Enable注解中洪囤,一般都會帶有一個@Import注解徒坡,比如@EnableScheduling注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({SchedulingConfiguration.class})
@Documented
public @interface EnableScheduling {
}

所以,使用@Enable注解后瘤缩,Spring會解析到@Import導(dǎo)入的配置類喇完,并且根據(jù)這個配置類中的描述來實現(xiàn)Bean的裝配。

EnableAutoConfiguration注解

當(dāng)我們查看@EnableAutoConfiguration這個注解的時候剥啤,可以看到除了@Import注解之外锦溪,還有一個@AutoConfigurationPackage注解,而且@Import注解導(dǎo)入的并不是一個Configuration的配置類铐殃,而是AutoConfigurationImportSelector類海洼,那AutoConfigurationImportSelector里面包含什么東西呢?

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

AutoConfigurationImportSelector

AutoConfigurationImportSelector這個類實現(xiàn)了ImportSelector富腊,它只有一個selectImports抽象方法坏逢,并且返回一個String數(shù)組,這個數(shù)組中的值就是需要裝配到IoC容器中的類赘被,當(dāng)在@Import中導(dǎo)入一個ImportSelector的實現(xiàn)類后是整,會把該實現(xiàn)類中返回的class名稱都裝載到IoC容器中。

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
}

和@Confifguration不同的是民假,ImportSelector可以實現(xiàn)批量裝配浮入,而且可以通過邏輯處理來實現(xiàn)Bean的選擇性裝配,也就是可以根據(jù)上下文來決定哪些類可以被裝配到IoC容器中羊异,下面通過一個例子介紹下ImportSelector的使用:

  1. 首先創(chuàng)建兩個類事秀,我們要把這兩個類裝配到IoC容器中
public class FirstClass {
...
}
public class SecondClass {
...
}
  1. 創(chuàng)建ImportSelector的實現(xiàn)類,在實現(xiàn)類中把上面定義的兩個類加入到數(shù)組中
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {FirstClass.class.getName(), SecondClass.class.getName()}
    }
}
  1. 創(chuàng)建一個類似EnableAutoConfiguration的注解野舶,通過@Import導(dǎo)入MyImportSelector
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({MyImportSelector.class})
public @interface MyAutoImport {
}
  1. 創(chuàng)建啟動類易迹,在啟動類上使用MyAutoImport注解,然后通過ConfigurableApplicationContext獲取FirstClass
@SpringBootApplication
@MyAutoImport
public class ImportSelectorDemo {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringBootApplication.run(ImportSelectorDemo.class);
        FirstClass fc = context.getBean(FirstClass.class);
    }
}

這種方式相比于@Import(*Configuration.class)的好處在于裝配的靈活性平道,也可以實現(xiàn)批量裝配睹欲。在MyImportSelector的String數(shù)組中可以定義多個Configuration類,一個配置類代表的就是某一個技術(shù)組件中批量的Bean的聲明一屋,所以在自動裝配這個過程中只需要掃描到指定路徑下對應(yīng)的配置類即可窘疮。

自動裝配原理

自動裝配的核心是掃描約定目錄下的文件進(jìn)行解析,解析完成之后把得到的Configuration配置類通過ImportSelector進(jìn)行導(dǎo)入冀墨,進(jìn)而完成Bean的自動裝配過程闸衫。

我們看一下AutoConfigurationImportSelector中的selectImports方法,它是ImportSelector接口的實現(xiàn)诽嘉,該方法中主要做了兩件事:

  • AutoConfigurationMetadataLoader.loadMetadata方法從META-INF/spring-autoconfigure-metadata.properties文件中加載自動裝配的條件元數(shù)據(jù)楚堤,也就是只有滿足條件的Bean才會被裝配
  • autoConfigurationEntry.getConfigurations()方法收集所有符合條件的配置類疫蔓,進(jìn)行自動裝配
public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

接下來我們了解下getAutoConfigurationEntry這個方法含懊,這個方法會掃描指定路徑下的文件進(jìn)行解析身冬,從而得到所需要裝配的配置類,它主要做了下面幾件事:

  • getAttributes方法獲得@EnableAutoConfiguration注解中的屬性exclude岔乔、excludeName等酥筝。
  • getCandidateConfiguration方法獲得所有自動裝配的配置類。
  • removeDuplicates方法去掉重復(fù)的配置項雏门。
  • getExclusions方法根據(jù)@EnableAutoConfiguration注解中配置的exclude等屬性嘿歌,把不需要自動裝配的配置類移除。
  • fireAutoConfigurationImportEvents廣播事件茁影。
  • 最后返回經(jīng)過多層判斷和過濾之后的配置類集合
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

總的來說宙帝,它先獲得所有的配置類,通過去重募闲、exclude排除等操作步脓,得到最終需要實現(xiàn)自動裝配的配置類。其中g(shù)etCandidateConfigurations方法是獲得配置類最核心的方法浩螺。

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靴患,它是Spring內(nèi)部提供的一種約定俗成的加載方式,和Java的SPI類似要出。它會掃描classpath下的META-INF/spring.factories文件鸳君,spring.factories文件中的數(shù)據(jù)以key=value的形式存儲,SpringFactoriesLoader.loadFactoryNames()會根據(jù)key的到對應(yīng)的value值患蹂,因此或颊,在自動裝配這個場景中,key對應(yīng)為EnableAutoConfiguration传于,value是多個配置類囱挑,也就是getCandidateConfigurations方法的返回值。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
......

如果我們打開RabbitAutoConfiguration格了,可以看到它就是一個基于JavaConfig形式的配置類:

@Configuration
@ConditionalOnClass({RabbitTemplate.class, Channel.class})
@EnableConfigurationProperties({RabbitProperties.class})
@Import({RabbitAnnotationDrivenConfiguration.class})
public class RabbitAutoConfiguration {
    public RabbitAutoConfiguration() {
    }

    @Configuration
    @ConditionalOnClass({RabbitMessagingTemplate.class})
    @ConditionalOnMissingBean({RabbitMessagingTemplate.class})
    @Import({RabbitAutoConfiguration.RabbitTemplateConfiguration.class})
    protected static class MessagingTemplateConfiguration {
        protected MessagingTemplateConfiguration() {
        }

        @Bean
        @ConditionalOnSingleCandidate(RabbitTemplate.class)
        public RabbitMessagingTemplate rabbitMessagingTemplate(RabbitTemplate rabbitTemplate) {
            return new RabbitMessagingTemplate(rabbitTemplate);
        }
   ......
 }

除了@Configuration注解看铆,還有一個@ConditionalOnClass注解,這個條件控制機(jī)制在這里的用途是判斷classpath下是否存在RabbitTemplate和Channel這兩個類盛末,如果有弹惦,則把當(dāng)前配置類注冊到IoC容器中。@EnableConfigurationProperties是屬性配置悄但,我們可以按照約定在application.properties中配置RabbitMQ的參數(shù)棠隐,這些配置會加載到RabbitProperties中。

到此處檐嚣,自動裝配的工作流程就結(jié)束了助泽,其實主要的核心過程是如下幾點:

  • 通過@Import(AutoConfigurationImportSelector)實現(xiàn)配置類的導(dǎo)入
  • AutoConfigurationImportSelector類實現(xiàn)了ImportSelector接口啰扛,重寫了方法selectImports,用于實現(xiàn)批量配置類的裝載嗡贺。
  • 通過spring提供的SpringFactoriesLoader機(jī)制掃描classpath下META-INF/spring.factories文件隐解,讀取需要實現(xiàn)自動裝配的配置類。
  • 通過條件篩選诫睬,把不符合條件的配置類移除煞茫,最終完成自動裝配。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末摄凡,一起剝皮案震驚了整個濱河市续徽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌亲澡,老刑警劉巖钦扭,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異床绪,居然都是意外死亡客情,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門会涎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裹匙,“玉大人,你說我怎么就攤上這事末秃「乓常” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵练慕,是天一觀的道長惰匙。 經(jīng)常有香客問我,道長铃将,這世上最難降的妖魔是什么项鬼? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮劲阎,結(jié)果婚禮上绘盟,老公的妹妹穿的比我還像新娘。我一直安慰自己悯仙,他們只是感情好龄毡,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锡垄,像睡著了一般沦零。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上货岭,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天路操,我揣著相機(jī)與錄音疾渴,去河邊找鬼。 笑死屯仗,一個胖子當(dāng)著我的面吹牛搞坝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播祭钉,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼瞄沙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了慌核?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤申尼,失蹤者是張志新(化名)和其女友劉穎垮卓,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體师幕,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡粟按,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了霹粥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灭将。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖后控,靈堂內(nèi)的尸體忽然破棺而出庙曙,到底是詐尸還是另有隱情,我是刑警寧澤浩淘,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布捌朴,位于F島的核電站,受9級特大地震影響张抄,放射性物質(zhì)發(fā)生泄漏砂蔽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一署惯、第九天 我趴在偏房一處隱蔽的房頂上張望左驾。 院中可真熱鬧,春花似錦极谊、人聲如沸诡右。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽稻爬。三九已至,卻和暖如春蜕依,著一層夾襖步出監(jiān)牢的瞬間桅锄,已是汗流浹背琉雳。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留友瘤,地道東北人翠肘。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像辫秧,于是被迫代替她去往敵國和親束倍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359