SpringBoot 自動裝配原理

SpringBoot 自動裝配原理

SpringBoot 基本特性

  • AutoConfiguration 自動裝配

  • Starter

  • Actuator

  • SpringBoot CLI

我們?yōu)槭裁磿褂肧pringBoot?

讓我們回想一下,在springboot沒有出現(xiàn)之前,我們通常使用SSM(Spring、SpringMVC、Mybatis)架構(gòu)搭建應(yīng)用程序,用過Spring框架的同學(xué)知道,我們通常稱為“萬能膠”,因為Spring社區(qū)擅長繼承各類框架;既然擅長集成各類框架,哪就免不了一些配置,在沒有Annotation出現(xiàn)之前,全部都是xml配置,回想一下,當(dāng)時我們搭建一個框架需要多少xml配置?是不是想想都頭疼;在Annotation出現(xiàn)之后變減少了一些xml。

我們?yōu)槭裁磿褂肧pringBoot呢?當(dāng)然是因為使用起來簡潔、方便呀,什么簡單?配置簡單,拿過來一個starter,運行即可,無需額外的配置便可啟動運行。

那么SpringBoot是如何做到這么簡單的配置呢?讓我們來看接下來的解答。

約定優(yōu)于配置

在軟件開發(fā)領(lǐng)域當(dāng)中,我們或多或少聽說過這個概念,其實SpringBoot就是這一概念的主要體現(xiàn)。

  • maven 的目錄結(jié)構(gòu)

    • 默認(rèn)resources文件夾存放配置文件

    • 默認(rèn)打包方式為jar

  • 提供開箱即用starter

  • 默認(rèn)配上文件application.properties/yml

  • 等等...

基本注解(介紹注解的主要目的是為了以后分析自動裝配原理做鋪墊)

SpringBoot 作為微服務(wù)的一個實現(xiàn)框架,其實并沒有什么新的技術(shù)產(chǎn)生,都是依賴于Spring中原有的技術(shù)來封裝的。

讓我們從@SpringBootApplication注解入手來分析SpringBoot中自動裝配相關(guān)注解

@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) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {}

@SpringBootApplication 注解是一個復(fù)合注解,我們主要分析@ComponentScan、@SpringBootConfiguration、@EnableAutoConfiguration 注解。

@ComponentScan

這個注解打架接觸的最多,相當(dāng)于在xml中配置context:component-scan,它的作用就是掃描指定路徑下需要自動裝配的類。

標(biāo)識需要自動裝配類的注解為:@Component、 @Repository、@Service、@Controller。

@ComponentScan 默認(rèn)會掃描當(dāng)前package下所有標(biāo)注相關(guān)注解的類,將其注入到Spring的IoC中。

@SpringBootConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false)
public @interface SpringBootConfiguration {}

@SpringBootConfiguration 注解其實是@Configuration注解的一個封裝,@Configuration注解我們應(yīng)該不會陌生,他是基于javaConfig形式的基于SpringIoC容器的配置類的一種注解。

@Configuration 注解的作用是聲明一個javaConfig配置類,而配置類中的任何一個@Bean的方法,它的返回值都會作為Bean的定義注冊到SpringIoc中,方法名默認(rèn)就是這個bean的id。

@EnableAutoConfiguration

@EnableAutoConfiguration 注解其實也不是什么新的注解,我們應(yīng)該經(jīng)??吹竭^以@Enable開頭的注解,這里不詳細(xì)解釋了。

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

@EnableAutoConfiguration 這個復(fù)合注解中有兩個主要的注解,@AutoConfigurationPackage、@Import注解,這里我們先簡單解釋一下這兩個注解作用,稍后結(jié)合SpringBoot自動裝配的原理進(jìn)行解釋。

@Import 注解的作用就是將其他位置的javaConfig配置類導(dǎo)入到當(dāng)前環(huán)境下,進(jìn)行加載相應(yīng)的Bean。

@AutoConfigurationPackage 注解下其實也是使用@Import注解。

@Import注解 和 @AutoConfiguration注解雖然在功能上作用是一樣的,但是是不同的兩種方式加載Bean到IoC容器中。

深入分析SpringBoot 自動裝配原理

通過前面的分析我們知道,SpringBoot自動裝配機制主要發(fā)生在@EnableAutoConfiguration這個注解上,那么下面我們將詳細(xì)分析下這個注解。

Selector 裝配方式 和 Register 裝配方式

SpringBoot 中有兩種自動裝配的方式,一個是實現(xiàn) ImportSelector接口,一個是實現(xiàn) ImportBeanDefinitionRegistrar接口,我們這里只介紹ImportSelector方式,ImportBeanDefinitionRegistrar方式請讀者自行查閱。

AutoConfigurationImportSelector 類(實現(xiàn)ImportSelector接口方式)

首先我們先看下AutoConfigurationImportSelector類結(jié)構(gòu)關(guān)系和源碼


image
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
  @Override
  public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
    }
    // 加載元數(shù)據(jù),獲取需要過濾調(diào)類的一些信息
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
      .loadMetadata(this.beanClassLoader);
    // 加載類路徑下 META-INF/spring.factories 文件和排除不需要的配置類
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);
    // 返回所有的配置類名稱的數(shù)組
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  }
}

上面的源碼就是自動配置的所有邏輯,首先通過注解獲取配置注解上的元數(shù)據(jù),例如:exclude 屬性;其次通過getAutoConfigurationEntry()方法加載類路徑下所有META-INFO文件夾下的spring.factories文件并進(jìn)行解析,處理;最后將處理好的配置類名稱以數(shù)組的形式返回。

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
    // 獲取注解上的元數(shù)據(jù)
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 獲取所有META-INFO/spring.factories配置文件
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
    // 獲取所有需要排除的配置類
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
    // 移除需要排除的配置類
        configurations.removeAll(exclusions);
    // 過濾掉不符合condition的配置類
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

上述的代碼邏輯非常的清晰,這里就不多做介紹了,我們看下getCandidateConfigurations()這個方法。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                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的loadFactoryNames()加載所有的spring.factories文件。那么spring.factories文件和我們的自動裝配有什么關(guān)系呢?讓我們看下面的圖片

image

上圖是spring.factories文件的一部分,其中key為 org.springframework.boot.autoconfigure.EnableAutoConfiguration的有好多配置類的全路徑,SpringBoot就是根據(jù)約定大于配置的原則,將配置類寫到 key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的value中,然后通過SpringFactoriesLoader.loadFactoryNames()方法進(jìn)行加載,代碼如下。

// 加載META-INF/spring.factories 文件的邏輯
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                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;
    }

// 返回EnableAutoConfiguration類型的Class
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }

// 根據(jù)class類型加載配置文件中的信息
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

// 具體讀取配置文件的信息
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()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryTypeName, factoryImplementationName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

到此為止SpringBoot自動裝配的邏輯結(jié)束,接下來就是Spring容器進(jìn)行刷新后同構(gòu)后置處理器進(jìn)行調(diào)用后注冊到SpringIoC容器中。

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

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

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