Spring Boot - 原理深刻 - 自動配置

2.2.2 自動配置

​ 可以在咱們添加jar包依賴時,自動爲咱們進行配置一下配置,咱們能夠不須要配置或者少許配置就能運行編寫的項目。java

問題:Spring Boot究竟是如何進行自動配置的,都把那些組件進行了自動配置?web

2.2.2.1 @SpringBootApplication

Spring Boot 應用啓動的入口是@SpringBootApplication註解標註類的main方法,spring

@SpringBootApplication可以掃描Spring組件而且自動配置Spring Boot markdown

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

@SpringBootApplication註解類框架

// 註解的適用範圍:類、接口、枚舉
@Target({ElementType.TYPE})
// 註解的生命週期:運行時
@Retention(RetentionPolicy.RUNTIME)
// 標明註解可標註在javadoc中
@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 {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

從上面能夠看出,@SpringBootApplication註解主要由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan這三個核心註解組成。ide

2.2.2.1.1 @SpringBootConfiguration

@SpringBootConfiguration 註解標明其類爲Spring Boot配置類spring-boot

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 配置到IOC容器
@Configuration
public @interface SpringBootConfiguration {
}

從上述能夠看出,@SpringBootConfiguration註解類主要註解爲@Configuration註解,該註解由Spring框架提供,表示當前類爲一個配置類,而且能夠被組件掃描器掃描。工具

2.2.2.1.2 @EnableAutoConfiguration

@EnableAutoConfiguration註解表示爲自動配置類,該註解是Spring Boot最重要的註解,也是實現自動配置的註解。this

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

從源碼能夠發現,@EnableAutoConfiguration註解爲一個組合註解,其做用就是藉助@Import註解導入特定場景須要向IOC註冊的Bean,而且加載到IOC容器。@AutoConfigurationPackage就是藉助@Import來蒐集全部符合自動配置條件的Bean定義,而且加載到IOC容器中。url

  • @AutoConfigurationPackage

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    // 導入Registrar中註冊的組件
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    
    }

    從上述源碼中能夠看出@AutoConfigurationPackage註解的功能是有@Import註解實現的。@Import它是Spring框架底層註解,它的做用就是給容器導入某個組件類

    Registrar類源碼

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
              // 將主程序類所在的包以及全部子包下的組件掃描到Spring容器
            register(registry, new PackageImport(metadata).getPackageName());
        }
    
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImport(metadata));
        }
    
    }

    從上述能夠看出,@AutoConfigurationPackage註解的主要做用就是將主程序類所在的包以及全部子包下的組件加載到IOC容器中。

    所以:在定義項目包目錄時,要求定義的包結構必須規範,項目主程序啓動類要放在最外層的根目錄位置,而後在根目錄的位置內部創建子包和類進行業務開發,這樣才能保證定義的類才能被組件掃描器掃描。

  • @Import({AutoConfigurationImportSelector.class})

    將AutoConfigurationImportSelector類導入到Spring容器中。

    AutoConfigurationImportSelector能夠幫助 Spring Boot 應用將全部符合條件@Configuration的配置都導入到當前Spring Boot建立並使用的IOC容器(ApplicationContext)中。

    // 自動配置的過程
    public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
      Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                   () -> String.format("Only %s implementations are supported, got %s",
                                       AutoConfigurationImportSelector.class.getSimpleName(),
                                       deferredImportSelector.getClass().getName()));
      // 獲取自動配置的配置類
      AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
          .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
      this.autoConfigurationEntries.add(autoConfigurationEntry);
      for (String importClassName : autoConfigurationEntry.getConfigurations()) {
          this.entries.putIfAbsent(importClassName, annotationMetadata);
      }
    }
    
    // 獲取自動配置元信息
    private AutoConfigurationMetadata getAutoConfigurationMetadata() {
      if (this.autoConfigurationMetadata == null) {
          // 加載自動配置元信息,須要傳入beanClassLoader這個類加載器
          this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
      }
      return this.autoConfigurationMetadata;
    }
    
    // 獲取自動配置的配置類
    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
          // 從META-INF/spring.factories配置文件中將對於的自動配置類獲取到
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

深刻AutoConfigurationMetadataLoader.loadMetadata()方法

// 文件中須要加載的配置類的類路徑
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";

private AutoConfigurationMetadataLoader() {
}

public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
    return loadMetadata(classLoader, PATH);
}

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
    try {
        // 讀取spring-boot-autoconfigure-2.1.14.RELEASE.jar中spring-autoconfigure-metadata.properties的信息生成URL
        Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
            : ClassLoader.getSystemResources(path);
        Properties properties = new Properties();
        while (urls.hasMoreElements()) {
            properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
        }
        return loadMetadata(properties);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
    }
}

深刻AutoConfigurationImportSelector.getCandidateConfigurations() 方法

這個方法有一個重要的loadFactoryNames方法,這個方法讓SpringFactoriesLoader去加載一些組件的名字。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 這個方法須要兩個參數,getSpringFactoriesLoaderFactoryClass()、getBeanClassLoader()
    // getSpringFactoriesLoaderFactoryClass() 返回的:EnableAutoConfiguration.class
    // getBeanClassLoader() 返回的:beanClassLoader類加載器
    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;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

protected ClassLoader getBeanClassLoader() {
    return this.beanClassLoader;
}

繼續深刻loadFactoryNames()方法

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        // 獲取出的健
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, 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 {
            // 若是類加載器不爲空,這加載類路徑下的META-INF/spring.factories,將其中設置的配置類的類路徑信息封裝爲Enumeration對象
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources("META-INF/spring.factories") :
                    ClassLoader.getSystemResources("META-INF/spring.factories"));
            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 factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

會去讀取一個 spring.factories 的文件,讀取不到會報錯

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

它實際上是去加載一個外部的文件,而這個文件是在

Spring Boot - 原理深刻 - 自動配置

Spring Boot - 原理深刻 - 自動配置

@EnableAutoConfiguration 註解就是從classpath中搜尋META-INF/spring.factories配置文件,並將其org.springframework.boot.autoconfigure.EnableAutoConfiguration對於的配置經過反射實例化對應的標註了@Configuration的JavaConfig配置類,而且加載到IOC容器中。

以web項目爲例,在項目中加入了web環境依賴啓動器,對應的org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration自動配置就會生效,打開自動配置就會發現,在配置類中經過全註解的方式對 Spring MVC 運行環境所須要環境進行了默認配置,包括前綴、後綴、試圖解析器、MVC校驗器等。

總結

Spring Boot 底層自動配置的步驟:

1)Spring Boot 應用啓動

2)@SpringBootApplication 註解起做用

3)@EnableAutoConfiguration 註解起做用

4)@AutoConfigurationPackage 註解起做用

​ @AutoConfigurationPackage 這個註解主要做用就是@Import({AutoConfigurationPackages.Registrar.class}),它經過Registrar類導入容器中,而Registrar的做用就是將掃描主配置類的包以及子包,並將對應的組件導入IOC容器中。

5)@Import(AutoConfigurationImportSelector.class)

​ @Import(AutoConfigurationImportSelector.class) 它將 AutoConfigurationImportSelector 類導入容器中,AutoConfigurationImportSelector 類的做用是經過getAutoConfigurationEntry()方法執行的過程當中,會使用內部工具類SpringFactoriesLoader,查找classpath上全部的jar包中的META-INF/spring.factories進行加載,實現將配置類信息交給Spring Factory加載器進行一系列的容器建立過程。

2.2.2.1.3 @ComponentScan

@ComponentScan 註解具體掃描包的路徑,由Spring Boot主程序所在包的位置決定。在掃描的過程當中由@AutoConfigurationPackage註解進行解析,從而獲得Spring Boot主程序類所在包的具體位置

相關文章
相關標籤/搜索