自动配置原理

SpringBoot2 引导加载自动配置类

1、@SpringBootApplication <=> @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan

2、@SpringBootConfiguration:底层为 @Configuration,标识当前类为配置类

3、@ComponentScan:扫描包路径下的组件

4、@EnableAutoConfiguration

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration

(1)@AutoConfigurationPackage:自动配置包,指定了默认的包规则

//往容器中导入一个Registrar组件,再利用Registrar给容器中导入一系列组件
//导入指定的一个包下的所有组件
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage

(2)@Import(AutoConfigurationImportSelector.class)

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        //利用getAutoConfigurationEntry(annotationMetadata),给容器中批量导入一些组件
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        //调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }


    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        //利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
        List<String> configurations = new ArrayList<>(
            SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
        ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
        Assert.notEmpty(configurations,
                        "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
}
public final class SpringFactoriesLoader {

    /*
    加载META-INF/spring.factories
    默认扫描当前系统中所有包的META-INF/spring.factories
    */
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        result = new HashMap<>();
        try {
            //获取资源文件
            Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
            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();
                    String[] factoryImplementationNames =
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                    for (String factoryImplementationName : factoryImplementationNames) {
                        result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                            .add(factoryImplementationName.trim());
                    }
                }
            }

            // Replace all lists with unmodifiable lists containing unique elements
            result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                              .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
            cache.put(classLoader, result);
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                                               FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
        return result;
    }
}

 

按需开启自动配置项

1、spring-boot-autoconfigure 的 jar 包

(1)SpringBoot3 以下:存在 META-INF/spring.factories 文件

(2)SpringBoot3 及以上:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

2、文件中写死 Spring Boot 启动时,给容器中加载的所有配置类

3、所有自动配置启动的时候默认全部加载,xxxAutoConfiguration 都会按照条件装配规则(@Conditional),最终会按需配置

 

修改默认配置

1、Spring Boot 默认会在底层配好所有的组件,但优先应用自定义配置

2、顺序:xxxAutoConfiguration -> 组件 -> xxxProperties 中取值 ----> application.properties

(1)Spring Boot 先加载所有的自动配置类 xxxxxAutoConfiguration

(2)每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值,值存放在 xxxxProperties 中,xxxProperties 和配置文件进行绑定

(3)生效的配置类给容器中装配对应组件,即容器开启对应功能

3、定制化配置

(1)方式一:自定义 @Bean 替换底层组件

(2)方式二:修改底层组件,对应配置文件的值

 

SpringBoot3 自动配置流程(以 Web 场景为例)

1、导入 spring-boot-starter-web:导入 Web 开发场景

(1)场景启动器导入了相关场景的所有依赖:spring-boot-starter-json、spring-boot-starter-tomcat、spring-boot-spring-webmvc

(2)每个场景启动器都引入一个 spring-boot-starter(核心场景启动器)

(3)核心场景启动器引入 spring-boot-autoconfigure

(4)spring-boot-autoconfigure 包括所有场景的所有配置

(5)只要 spring-boot-autoconfigure 包下的所有类都能生效,那么相当于 SpringBoot 官方的整合功能就生效

(6)SpringBoot 默认扫描启动类所在包及其子包,而不扫描 spring-boot-autoconfigure 下写好的所有配置类

2、主程序:@SpringBootApplication

(1)@SpringBootApplication 由三个注解组成:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan

(2)SpringBoot 默认只扫描主程序所在的包及其下面的子包,扫描不到 spring-boot-autoconfigure 包中官方写好的配置类

3、@EnableAutoConfiguration:SpringBoot 开启自动配置的核心

(1)由 @Import(AutoConfigurationImportSelector.class) 提供功能:批量给容器中导入组件

(2)SpringBoot 启动默认加载所有配置类

(3)所有默认配置类是由 spring-boot-autoconfigure 下的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件指定

(4)项目启动时利用 @Import 批量导入组件机制把 autoconfigure 包下的所有 xxxAutoConfiguration 类导入进来(自动配置类)

(5)按需生效:每一个自动配置类,都有条件注解 @ConditionalOnxxx,只有条件成立,才能生效

4、xxxAutoConfiguration 自动配置类

(1)给容器中使用 @Bean 放一堆组件

(2)每个自动配置类都可能有这个注解 @EnableConfigurationProperties(xxxProperties.class),用来把配置文件中配的指定前缀的属性值封装到 xxxProperties 属性类中

(3)往容器中导入的所有组件的一些核心参数,都来自于 xxxProperties 类,xxxProperties 类都是和配置文件绑定

(4)只需要改配置文件的值,核心组件的底层参数就能修改

(5)以 Tomcat 为例:@EnableConfigurationProperties(ServerProperties.class) -> @ConfigurationProperties(prefix = "server", ignoreUnknownFileds = "true"):把服务器的所有以 server 为前缀的配置,都封装到了属性类中,并忽略未知配置

5、业务

 

核心流程总结

1、导入相关场景 starter -> 导入 spring-boot-starter -> 导入 spring-boot-autoconfigure

2、autoconfigure 包中文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,指定启动时所要加载的自动配置类

3、@EnableAutoConfiguration 自动导入所有自动配置类

4、自动配置类 xxxAutoConfiguration

(1)存在条件注解 @ConditionalOnxxx,进行按需加载

(2)给容器中导入一堆组件,组件都是从 xxxProperties 类中提取属性值

5、xxxProperties 类和配置文件进行绑定

posted @   半条咸鱼  阅读(82)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示