SpringBoot 自動裝配原理

功能分析

傳統(tǒng)的Spring項目會有很多的配置文件妻献,比如我們要使用Redis,一般除了對應(yīng)的依賴的jar包我們還需要在application.xml里面配置JedisConnectionFactory乏矾、JedisPoolConfig屋剑、RedisTemplate。但是如果使用SpringBoot的話诗眨,系統(tǒng)會根據(jù)pom.xml里面的jar包唉匾,自動生成這些類并且注入到IOC容器當中。

  1. 傳統(tǒng)Spring項目中需要配置
<bean id="jedisConnectionFactory" class="...JedisConnectionFactory"></bean>
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"></bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"></bean>
  1. 而使用SpringBoot的話匠楚,除了pom.xml引入相應(yīng)的jar包外巍膘,只需要在application.properties配置對應(yīng)的屬性值即可

概述

自動裝配的過程:

  1. 通過各種注解+繼承,引入包含自動裝配核心方法的類
  2. SpringApplication.run(Application.class, args)在運行時芋簿,調(diào)用自動裝配方法
  3. 自動裝配方法會讀取spring-boot-autoconfigure.jar里面的spring.factories配置文件峡懈,配置文件中有所有自動裝配類的配置類的類名
  4. 生成對應(yīng)功能的Configuration類,這些功能配置類要生效的話与斤,會去classpath中找是否有該類的依賴類(也就是pom.xml必須有對應(yīng)功能的jar包才行)
  5. 配置類里再通過判斷生成最后的功能類肪康,并且配置類里面注入了默認屬性值類,功能類可以引用并賦默認值撩穿。生成功能類的原則是自定義優(yōu)先磷支,沒有自定義時才會使用自動裝配類。

綜上所述食寡,要想自動裝配一個類需要滿足2個條件:

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

自動裝配的結(jié)果:

  1. 根據(jù)各種判斷和依賴雾狈,最終生成了業(yè)務(wù)需要的類并且注入到IOC容器當中了
  2. 自動裝配生成的類賦予了一些默認的屬性值

注解引用線路圖

復(fù)合注解+@import加載了對應(yīng)的類進來,然后在程序啟動方法里面抵皱,間接調(diào)用自動加載類的方法

@SpringBootApplication -->@EnableAutoConfiguration -->@Import(EnableAutoConfigurationImportSelector.class)
-->extends AutoConfigurationImportSelector -->selectImports() -->getExcludeAutoConfigurationsProperty()
通過注解引用善榛,最終在SpringApplication.run()方法的時候,會調(diào)用selectImports()呻畸,最終加載自動裝配

private List<String> getExcludeAutoConfigurationsProperty() {
            RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(this.environment, "spring.autoconfigure.");
    }

Redis自動裝配示例

  1. 從spring-boot-autoconfigure.jar/META-INF/spring.factories中獲取120多個默認功能配置類移盆,其中包括redis的功能配置類RedisAutoConfiguration的全限定名
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
  1. RedisAutoConfiguration配置類生效的一個條件是@ConditionalOnClass :JedisConnection.class, RedisOperations.class, Jedis.class,所以會去classpath下去查找對應(yīng)的class文件
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
        @Bean
        @ConditionalOnMissingBean(RedisConnectionFactory.class)
        public JedisConnectionFactory redisConnectionFactory()
                throws UnknownHostException {
            return applyProperties(createJedisConnectionFactory());
        }
}
  1. 如果pom.xml有對應(yīng)的jar包,就能匹配到對應(yīng)依賴class:JedisConnection.class, RedisOperations.class, Jedis.class
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
  1. 匹配成功,這個功能配置類才會生效伤为,同時會注入默認的屬性配置類@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;
  1. Redis功能配置里面會根據(jù)條件生成最終的JedisConnectionFactory味滞、RedisTemplate,條件就是IOC環(huán)境里面,沒有用戶自定義的@ConditionalOnMissingBean(RedisConnectionFactory.class)钮呀、RedisTemplate
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
        @Bean
        @ConditionalOnMissingBean(RedisConnectionFactory.class)
        public JedisConnectionFactory redisConnectionFactory()
                throws UnknownHostException {
            return applyProperties(createJedisConnectionFactory());
        }

        @Bean
        @ConditionalOnMissingBean(name = "redisTemplate")
        public RedisTemplate<Object, Object> redisTemplate(
                RedisConnectionFactory redisConnectionFactory)
                        throws UnknownHostException {
            RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
}
  1. 最終創(chuàng)建好的默認裝配類剑鞍,會通過功能配置類里面的 @Bean注解,注入到IOC當中

核心注解

@SpringBootApplication整合了3個注解:SpringBootConfiguration爽醋、EnableAutoConfiguration蚁署、ComponentScan

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
}
  1. @SpringBootConfiguration:實際上就是@Configuration,表明這是一個IOC容器的配置類蚂四,相當于說明該bean是一個spring中的xml文件光戈。
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
}
  1. @ComponentScan:指定了Spring中的指定MVC環(huán)境掃描包和Spring IOC的掃描包哪痰,掃描路徑就是該類所在的所有包。SpringBoot的注解掃描所有的同路徑下的類久妆,@Controller類歸位MVC類晌杰,其它類為Spring的類
常規(guī)mvc配置指定包
<context:component-scan base-package="com.test.Action" />  

Spring也要指定Spring的注解類的掃描路徑
<context:component-scan base-package="com.test" />
  1. @EnableAutoConfiguration:表示開啟Spring Boot自動配置功能,Spring Boot會根據(jù)應(yīng)用的依賴筷弦、自定義的bean肋演、classpath下有沒有某個類等等因素來猜測你需要的bean,然后注冊到IOC容器中烂琴。
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
  1. @Conditional注解表示在滿足某種條件后才初始化一個bean或者啟用某些配置爹殊。自定義編寫條件類,實現(xiàn)Condition接口奸绷,并覆蓋它的matches()方法,比如MyService類依賴于JdbcTemplateCondition.class個條件類梗夸,而JdbcTemplateCondition.class調(diào)節(jié)類的滿足條件是在classpath下面可以加載JdbcTemplate這個類。
@Conditional(JdbcTemplateCondition.class)
@Service
public MyService service() {
    ......
}

public class JdbcTemplateCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        try {
        conditionContext.getClassLoader().loadClass("org.springframework.jdbc.core.JdbcTemplate");
            return true;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return false;
    }
}
  1. @ConditionalOnClass:表示只要在classpath下找得到對應(yīng)的class文件号醉,該配置類反症、或者方法才會生效。
    比如classpath中有Billy.class畔派,這個配置類才生效惰帽,也就是Fighter這個Bean才會注入到IOC容器中
@Configuration
@ConditionalOnClass({Billy.class})
public class VanConfig {
    @Bean
    public Fighter billy(){
        return new Billy();
    }
}
  1. @ConditionalOnBean:表示在IOC環(huán)境下,containsBean為true的時候父虑,才通過该酗。
    例如下面,即使classpath下存在Test.class士嚎,但是Test.class沒有注入到IOC中呜魄,也會報錯。HelloService注入失敗莱衩。
    @Bean
    @ConditionalOnBean(Test.class)
    public HelloService test(){
        return new HelloService();
    }
  1. @ConditionalOnMissingBean 這個是個很厲害的注解爵嗅,實現(xiàn)默認配置時自定義優(yōu)先。如果上下文(IOC環(huán)境)中已經(jīng)有這個Bean了就忽略笨蚁,沒有這個Bean的話睹晒,才執(zhí)行返回默認自動裝配Bean。
    比如我們應(yīng)用要依賴Animal接口括细,如果我們手動注入一個animal伪很,那么就以注入的bean為準,如果未注入奋单,則會被@ConditionalOnMissingBean檢測到锉试,就使用默認的AutoConfigAnimal作為bean。
@RestController
public class MyRun {
    @Autowired
    private Animal animal;

    @RequestMapping("/auto/home")
    public String home(){
        return animal.eat();
    }
}

@Component("animal")
public class Human implements Animal{
    public String eat() {
        return "eat rice";
    }
}

@Configuration
public class TestConfig {
    @Bean
    @ConditionalOnMissingBean(Animal.class)
    public Animal test(){
        return new AutoConfigAnimal();
    }
}

public class AutoConfigAnimal implements Animal{
    public String eat() {
        return "eat anything";
    }
}

依賴的注解(Redis示例)

  • @SpringBootApplication:sb項目應(yīng)用啟動類的注解览濒,其實是3個注解的組合:@SpringBootConfiguration呆盖、@EnableAutoConfiguration拖云、@ComponentScan,其中在自動裝配中起作用的是第二個

  • @EnableAutoConfiguration:表示SB應(yīng)用啟動自動裝配的功能(包括加載對應(yīng)的Bean到IOC容器中应又,且根據(jù)默認配置對屬性賦值)

  • @Import(EnableAutoConfigurationImportSelector.class):這個注解比較厲害宙项,可以把沒有注冊到IOC中的Bean強行注冊到IOC中,表示啟動自動配置功能需要引入EnableAutoConfigurationImportSelector.class才行

  • @Configuration

  • @ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class }):表示讓RedisAutoConfiguration配置類起作用的話,必須有包含這些類的jar包才行

  • @EnableConfigurationProperties(RedisProperties.class):表示默認引用RedisProperties.class里面的配置

  • @ConditionalOnMissingBean(RedisConnectionFactory.class):表示如果用戶沒有自定義注入RedisConnectionFactory.class類株扛,才會使用默認的JedisConnectionFactory

代碼邏輯:

自動裝配的過程

  1. 通過各種注解實現(xiàn)了類與類之間的依賴關(guān)系尤筐,容器在啟動的時候Application.run,會調(diào)用EnableAutoConfigurationImportSelector.class的selectImports方法(其實是其父類的方法)

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

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

  4. selectImports方法繼續(xù)調(diào)用filter(configurations, autoConfigurationMetadata);這個時候會根據(jù)這些BeanConfiguration里面的條件拢驾,來一一篩選奖磁,最關(guān)鍵的是
    @ConditionalOnClass,這個條件注解會去classpath下查找繁疤,jar包里面是否有這個條件依賴類咖为,所以必須有了相應(yīng)的jar包,才有這些依賴類稠腊,這個時候這些功能配置類才會生效

  5. 功能配置類生效后躁染,會獲取到依賴的默認屬性值類,里面有一些該功能的默認屬性值

  6. 功能配置類里面配置了最終的功能Bean架忌,這個時候會通過@ConditionalOnMissingBean先判斷用戶是否自定義了吞彤,如果用戶沒有自定義,就創(chuàng)建一個默認的功能類叹放,并且注入到IOC中

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
            Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
            List<String> result = new ArrayList<String>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }
            return result;
    }

spring.factories 文件:

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

參考博客

http://www.reibang.com/p/83693d3d0a65

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饰恕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子井仰,更是在濱河造成了極大的恐慌埋嵌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俱恶,死亡現(xiàn)場離奇詭異雹嗦,居然都是意外死亡,警方通過查閱死者的電腦和手機合是,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門了罪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人聪全,你說我怎么就攤上這事捶惜。” “怎么了荔烧?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵吱七,是天一觀的道長汽久。 經(jīng)常有香客問我,道長踊餐,這世上最難降的妖魔是什么景醇? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮吝岭,結(jié)果婚禮上三痰,老公的妹妹穿的比我還像新娘。我一直安慰自己窜管,他們只是感情好散劫,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著幕帆,像睡著了一般获搏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上失乾,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天常熙,我揣著相機與錄音,去河邊找鬼碱茁。 笑死裸卫,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的纽竣。 我是一名探鬼主播墓贿,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蜓氨!你這毒婦竟也來了募壕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤语盈,失蹤者是張志新(化名)和其女友劉穎舱馅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刀荒,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡代嗤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了缠借。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片干毅。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖泼返,靈堂內(nèi)的尸體忽然破棺而出硝逢,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布渠鸽,位于F島的核電站叫乌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏徽缚。R本人自食惡果不足惜憨奸,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凿试。 院中可真熱鬧排宰,春花似錦、人聲如沸那婉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽详炬。三九已至盐类,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間痕寓,已是汗流浹背傲醉。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工蝇闭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留呻率,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓呻引,卻偏偏與公主長得像礼仗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子逻悠,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

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