SpringBoot 自動裝配原理

功能分析

傳統(tǒng)的Spring項目會有很多的配置文件,比如我們要使用Redis,一般除了對應的依賴的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引入相應的jar包外,只需要在application.properties配置對應的屬性值即可

概述

自動裝配的過程:

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

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

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

自動裝配的結果:

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

注解引用線路圖

復合注解+@import加載了對應的類進來,然后在程序啟動方法里面,間接調(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下去查找對應的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有對應的jar包,就能匹配到對應依賴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ù)應用的依賴、自定義的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下找得到對應的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。
    比如我們應用要依賴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項目應用啟動類的注解,其實是3個注解的組合:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan,其中在自動裝配中起作用的是第二個

  • @EnableAutoConfiguration:表示SB應用啟動自動裝配的功能(包括加載對應的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)了類與類之間的依賴關系,容器在啟動的時候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相關的Bean的全限定名ClassName,大概120多個

  4. selectImports方法繼續(xù)調(diào)用filter(configurations, autoConfigurationMetadata);這個時候會根據(jù)這些BeanConfiguration里面的條件,來一一篩選,最關鍵的是
    @ConditionalOnClass,這個條件注解會去classpath下查找,jar包里面是否有這個條件依賴類,所以必須有了相應的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.itdecent.cn/p/83693d3d0a65

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容