江帅帅:Spring Boot 底层级探索系列 02 - 自动配置的底层逻辑

简介: 江帅帅:Spring Boot 底层级探索系列 02 - 自动配置的底层逻辑

江帅帅,微信公众号【江帅帅】作者 ,擅长系统架构设计,大数据,运维、机器学习等技术领域;对大中后台技术有丰富经验(交易平台、基础服务、智能客服、基础架构、智能运维、数据库、安全、IT 等方向);曾担任怀致科技 CTO,并还在东软集团、中国移动、多迪集团等企业中任职过相关技术负责人。

1. Spring Boot 的自动配置超详解

1.1 @SpringBootApplication 注解

Spring Boot 的启动类,也就是入口类,需要使用 @SpringBootApplication 注解来标注。在启动类中,我们的 main 方法就是 Java 应用程序的入口方法。

@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}
)}
)
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 {};
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

其中,比较重要的三个注解是: @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。

1.2 @SpringBootConfiguration 注解

主要是负责 Spring Boot 应用配置相关的注解,它也是组合注解,具体源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

通过源码,可以看到它也使用了 @Configuration 注解,它们两个都是将当前类标注为配置类,能将类中使用 @Bean 注解标记的方法对应的实例注入到 Spring 容器中,那实例名就是方法名。

另外在 @Configuration 注解源码中,还看到有一个 @Component 注解,做了再次封装,主要是把普通 POJO 实例化到 Spring 容器中。具体源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
    boolean proxyBeanMethods() default true;
}

所以,更推荐大家在 Spring Boot 应用中使用 @SpringBootConfiguration。

1.3 @EnableAutoConfiguration 注解

主要用来启动自动配置,Spring Boot 就能够根据依赖信息自动实现应用的相关配置,总体分为两个部分:一是收集所有 spring.factories 中EnableAutoConfiguration 相关 bean 的类,二是将得到的类注册到 Spring 容器中。将符合的配置都加载到 IoC 容器中。具体源码如下:

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

组件调用关系图,具体如下:

这张图,怎么去理解呢?其实是这样的,涉及到了 BeanFactory 的创建。Spring 框架中会调用 ApplicationContext 的 refresh 方法来启动 Spring 容器,然后就会创建 BeanFactory,接着扫描各种包,读取使用到了 @Configuration、@Import、@SpringBootApplication 等注解标注的类,然后生成 BeanDefinition 最终注册到 BeanFactory 中。

然后就交给 BeanFactoryPostProcessor 来执行,BeanFactory 后置处理器会处理 BeanDefinition,比如在 BeanFactoryPostProcessor 接口中,提供了 postProcessBeanFactory 方法来接收 ConfigurableListableBeanFactory 对象来处理。具体源码如下:

@FunctionalInterface
public interface BeanFactoryPostProcessor {
  void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

其他类似 @Configuration 等配置性质的注解,就让 ConfigurationClassPostProcessor 来处理。

上面的 ConfigurationClassPostProcessor 主要是 BeanFactoryPostProcessor 接口的实现类,主要是想从 BeanFactory 中获取所有 BeanDefinition 列表,遍历出那些使用了 @Configuration、@Import 等配置性质注解标注的类所对应的 BeanDefintion,然后进行注册。具体源码如下:

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
    PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {...}
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
  void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

具体,我们还可以去看看它的 parse 方法是如何处理的,它会去解析注解。

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            if (bd instanceof AnnotatedBeanDefinition) {
                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            }
            else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
            }
            else {
                parse(bd.getBeanClassName(), holder.getBeanName());
            }
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
        }
    }
    this.deferredImportSelectorHandler.process();
}

看到最后的 deferredImportSelectorHandler,这个内部类的里面有一个 deferredImportSelectors 集合,主要是用来添加 AutoConfigurationImportSelector。这个内部私有类,主要维护了一个类型为DeferredImportSelectorHolder 的 deferredImportSelectors 列表。这最后一句代码,就是处理完其他BeanDefinitions 后调用 process 方法。

再接着来看 process 方法,它负责自动配置类导入的内部实现,具体源码如下:

public void process() {
    List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    this.deferredImportSelectors = null;
    try {
        if (deferredImports != null) {
            DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
            deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
            deferredImports.forEach(handler::register);
            handler.processGroupImports();
        }
    }
    finally {
        this.deferredImportSelectors = new ArrayList<>();
    }
}

这个方法,需要这么来理解:

首先,DeferredImportSelector 它会去从 spring-boot-autoconfigure 包路径下的 META-INF/spring.factories 文件中找到 EnableAutoConfiguration 作为 key,然后获取对应的自动配置类列表。

第二步,在里面通过 key 即可找到对应需要自动配置的类。接着会进行遍历所有类名,加载和导入对应的配置类。

大致的思路是会先创建一个 ConfigurationClass 的对象,它会包含当前这个配置类,然后传进被调用的 doProcessConfigurationClass 方法中,然后处理该类包含的注解。如果是 @Import 注解,则会放在 processImports 方法中进行处理。

再具体讲,就是那些非 ImportSelector 接口实现类和ImportBeanDefinitionRegistrar 接口实现类的配置类,就会调用processConfigurationClass 方法来处理该自动配置类上面的其他注解,并将该自动配置类内部使用了 @Bean 注解的所有方法,条件化生成 bean 并注册到 Spring 容器,那最终就可以提供特定功能组件的默认实现,也就实现了 SpringBoot 的自动配置功能,在你使用的时候,比如直接通过 @Autowried 注解就可以注入某个功能组件,而不需要显示配置。

具体源码如下(这里不贴全部源码了,大家可以看看它给出的注释就明白了):

@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
    throws IOException {
    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
        // Recursively process any member (nested) classes first
        processMemberClasses(configClass, sourceClass);
    }
    // Process any @PropertySource annotations
    // 省略源码...
    // Process any @ComponentScan annotations
    // 省略源码...
    // Process any @Import annotations
    // 省略源码...
    // Process any @ImportResource annotations
    // 省略源码... 
    // Process individual @Bean methods
    // 省略源码...
    // Process default methods on interfaces
    // 省略源码...
    // Process superclass, if any
    // 省略源码...
    // No superclass -> processing is complete
    return null;
}

1.4 获取 Bean 类信息

我们可以来研究下这个注解,了解它是如何加载配置的。在源码中,可以看到 @Import({AutoConfigurationImportSelector.class}) 注解,导入的就是自动配置选择器。

AutoConfigurationImportSelector 选择器是 DeferredImportSelector 接口的实现类,会在 BeanFactory 中对所有 BeanDefinition 处理后执行来进行 SpringBoot 自动配置类的加载、导入操作等,并基于 @Conditional 条件化配置来决定是否将该配置类内部定义的 Bean 注册到 Spring 容器。具体源码如下:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
    ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {...}

AutoConfigurationImportSelector.class 中,可以看到实现了一个 selectImports 方法,用来导出 Configuration。方法中调用了 getAutoConfigurationEntry 方法,获取 bean 类信息。具体源码如下:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    // 获取自动配置的元数据
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
    // 获取 bean 类信息
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,  annotationMetadata);
    // 返回获取到的所有配置
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

继续来看 getAutoConfigurationEntry 方法,具体源码如下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 获取注解的元数据,主要有 exclude,excludeName 等
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 获取所有的 configurations
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 将获取到的 configurations 进行去重
    configurations = removeDuplicates(configurations);
    // 根据 exclusion 来删掉指定的类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

再接着来看调用的 getCandidateConfigurations 方法,它主要是想获取所有对应的配置,它里面调用了 loadFactoryNames 方法,目的是要想加载 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;
}

loadFactoryNames 方法的具体源码如下:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    // 这里获得的 factoryTypeName 主要就是 org.springframework.boot.autoconfigure.EnableAutoConfiguration
    String factoryTypeName = factoryType.getName();
    // 这里返回的是在 spring.factories 文件中那些 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的类路径
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

接着就在 loadSpringFactories 方法中,找到所有的 spring.factories 配置信息,然后全部返回。具体源码如下:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }
    try {
        // 获取所有 spring.factories 文件
        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);
    }
}

2. 未完待续,等我下一篇嗷 ~~~

目录
相关文章
|
5天前
|
存储 Java 数据安全/隐私保护
|
2天前
|
Java 开发者 Spring
Spring Boot中的资源文件属性配置
【4月更文挑战第28天】在Spring Boot应用程序中,配置文件是管理应用程序行为的重要组成部分。资源文件属性配置允许开发者在不重新编译代码的情况下,对应用程序进行灵活地配置和调整。本篇博客将介绍Spring Boot中资源文件属性配置的基本概念,并通过实际示例展示如何利用这一功能。
12 1
|
11天前
|
存储 安全 Java
第2章 Spring Security 的环境设置与基础配置(2024 最新版)(下)
第2章 Spring Security 的环境设置与基础配置(2024 最新版)(下)
18 0
|
11天前
|
安全 Java 数据库
第2章 Spring Security 的环境设置与基础配置(2024 最新版)(上)
第2章 Spring Security 的环境设置与基础配置(2024 最新版)
36 0
|
12天前
|
XML Java 数据库
在Spring框架中,XML配置事务
在Spring框架中,XML配置事务
|
12天前
|
Java Windows Spring
Spring Boot 3.x 全新的热部署配置方式(IntelliJ IDEA 2023.1)
Spring Boot 3.x 全新的热部署配置方式(IntelliJ IDEA 2023.1)
16 1
|
12天前
|
安全 Java Spring
Spring Security 5.7 最新配置细节(直接就能用),WebSecurityConfigurerAdapter 已废弃
Spring Security 5.7 最新配置细节(直接就能用),WebSecurityConfigurerAdapter 已废弃
31 0
|
12天前
|
安全 Java 应用服务中间件
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
27 0
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
|
17天前
|
XML Java 数据格式
Spring IOC—基于XML配置和管理Bean 万字详解(通俗易懂)
Spring 第二节 IOC—基于XML配置和管理Bean 万字详解!。
94 5
|
2月前
|
消息中间件 SpringCloudAlibaba Java
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
786 0