【SpringBoot】万字源码解析——自动装配与starter机制

时间:2024-10-26 15:43:27

自动装配机制

在传统的 Spring 框架中,开发者需要通过 XML 文件或 Java 配置类显式地声明 Bean 和各种配置项(例如数据源、事务管理、视图解析器等)。Spring Boot 的自动装配旨在减少这些繁琐的配置,通过默认的配置和条件装配,自动完成很多配置工作,从而减少开发者的配置量。

@SpringBootApplication注解

可以把 @SpringBootApplication 注解看作是 @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan 注解的集合。

  • @EnableAutoConfiguration:自动配置机制的核心注解。
  • @SpringBootConfiguration:作为 @Configuration 注解的扩展,它标识该类为 Spring 的配置类。在该类中可以定义 @Bean 方法,这些方法返回的对象将被注册到 Spring 容器中,由容器管理其生命周期。
  • @ComponentScan:从声明 @SpringBootApplication 的类所在的包开始,自动扫描并注册 @Component@Service@Repository 等注解的类到 Spring 容器中。它确保应用程序的组件、服务、控制器等能够被自动发现和注入。

@EnableAutoConfiguration核心注解

@EnableAutoConfiguration 通过 @Import 注解导入了 AutoConfigurationImportSelector 类。

@Import 注解的作用是将指定的配置类或 ImportSelector 导入到当前的配置类中。

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

AutoConfigurationImportSelector核心类

AutoConfigurationImportSelector 是自动装配的核心类,其实现了ImportSelector接口的 selectImports 方法。

selectImports()方法

该方法主要有两个作用:

  1. 判断自动装配开关是否打开,检查 yml 文件是否修改了 spring.boot.enableautoconfiguration=false
  2. 调用 getAutoConfigurationEntry()方法,获取需要自动装配的 bean
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 1.判断自动装配开关是否打开
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        // 2.获取所有需要自动装配的bean
        AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

getAutoConfigurationEntry()

getAutoConfigurationEntry() 方法的核心作用是在 Spring 启动时,对当前应用中的自动配置类进行去重筛选

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // 1.判断自动装配开关是否打开
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        // 2.获取@EnableAutoConfiguration注解中的exclude和excludeName属性
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        // *3.获取位于META-INF/spring.factories下候选的所有配置类
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        // 4.1.移除重复的类
        configurations = this.removeDuplicates(configurations);
        // 4.2.排除exclude和excludeName数组中包含的配置类
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        this.checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        // 4.3.根据当前应用环境进一步筛选配置类
        configurations = this.getConfigurationClassFilter().filter(configurations);
        // 5.触发自动配置事件
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }
}

getCandidateConfigurations()核心方法

跟进到 getCandidateConfigurations() 方法,主要通过 SpringFactoriesLoaderImportCandidates 来加载自动配置的候选类。

  • 使用 SpringFactoriesLoader 加载 META-INF/spring.factories 文件中定义的与 EnableAutoConfiguration.class 相关的实现类。
  • 通过 ImportCandidates 加载与 AutoConfiguration.class 相关的其他候选实现类,并将这些类添加到 configurations 列表中。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 1.调用SpringFactoriesLoader获取工厂的实现类
    List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
    // 2.使用ImportCandidates加载工厂的实现类
    ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
    Assert.notEmpty(configurations, "xxx.");
    return configurations;
}

最后,使用 Assert.notEmpty() 方法确保 configurations 列表中至少有一个候选类后,返回候选类。

条件化装配

常见注解

  • Bean 条件注解
    • @ConditionalOnBean:当容器里有指定 Bean 的条件下
    • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
    • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
  • Class 条件注解
    • @ConditionalOnClass:当类路径下有指定类的条件下
    • @ConditionalOnMissingClass:当类路径下没有指定类的条件下
  • 属性条件注解@ConditionalOnProperty:指定的属性是否有指定的值
  • Web 应用条件注解
    • @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
    • @ConditionalOnWebApplication:当前项目是 Web 项 目的条件下
  • 其他条件注解
    • @ConditionalOnResource:类路径是否有指定的值
    • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
    • @ConditionalOnJava:基于 Java 版本作为判断条件
    • @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置

SpringFactoriesLoader原理

SpringFactoriesLoader方法会根据传入的工厂类类加载器,从 META-INF/spring.factories 文件中加载 「指定类型对应的工厂类名称」

loadFactoryNames()

loadFactoryNames 方法是一个高级 API,它通过获取入参中的全限定类名 factoryTypeName,在内部调用 loadSpringFactories() 方法获取返回的 Map 集合,并根据 factoryTypeName 获取了 Map 中的实现类的 List 集合

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
	// 获取工厂类的全限定名
    String factoryTypeName = factoryType.getName();
    // 从 loadSpringFactories 返回的 Map 中获取指定类型工厂的实现类
    return (List)loadSpringFactories(classLoaderToUse)
        .getOrDefault(factoryTypeName, Collections.emptyList());
}

loadSpringFactories()

loadSpringFactories 方法是更加底层的方法,通过缓存机制类加载器获取 spring.factories 文件中所有配置的工厂及其实现类,将这些信息封装为 Map 集合后返回给上游的 API。

缓存机制

方法会检查是否已经通过当前类加载器加载过 spring.factories 文件。如果缓存 (cache) 中已经存在相应的工厂信息,直接返回缓存的 Map<String, List<String>>,避免重复加载。

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

加载 META-INF/spring.factories

方法会通过类加载器查找所有路径下名为 META-INF/spring.factories 的文件。由于每个 JAR 包都可能包含一个 META-INF/spring.factories 文件,方法会返回一个 Enumeration<URL> 对象,表示找到的所有相关资源文件。

Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");

解析spring.factories文件

通过迭代逐个读取每个找到的 spring.factories 文件。对于每个文件,使用 PropertiesLoaderUtils.loadProperties() 将文件内容解析为 Properties 对象。

每个 Properties 对象对应一个 spring.factories 文件的内容,其中 key 是工厂类型(例如 org.springframework.context.ApplicationContextInitializer),value 是逗号分隔的工厂实现类列表。

while(urls.hasMoreElements()) {
    URL url = (URL)urls.nextElement();
    UrlResource resource = new UrlResource(url);
    Properties properties = PropertiesLoaderUtils.loadProperties(resource);

将工厂类型和实现类存入Map

遍历 PropertiesentrySet()。对于每个 entrykey 是工厂类型的全限定类名,value 是对应的工厂实现类名(逗号分隔)。

工厂类型名称通过 entry.getKey() 获取,并使用 String.trim() 去除可能的空白字符。工厂实现类则将逗号分隔的字符串转换为实现类的数组。

while(var6.hasNext()) {
    Map.Entry<?, ?> entry = (Map.Entry)var6.next();
    String factoryTypeName = ((String)entry.getKey()).trim();
    String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
    String[] var10 = factoryImplementationNames;
    int var11 = factoryImplementationNames.length;

    for(int var12 = 0; var12 < var11; ++var12) {
        String factoryImplementationName = var10[var12];
        ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
            return new ArrayList();
        })).add(factoryImplementationName.trim());
    }
}

Spring Boot Starter机制

Spring Boot Starter 是一组方便的依赖描述符,旨在简化 Maven 和 Gradle 项目的依赖管理。每个 Starter 通常包含与某种功能或库相关的依赖,开发者只需引入相应的 Starter,就可以自动获得这些依赖及其配置。

实现原理

依赖管理

Spring Boot Starter本质上是一个Maven或Gradle依赖,包含了一组已配置好的常用库。例如,spring-boot-starter-web包含了Spring MVC、Jackson(用于JSON处理)、Tomcat(作为默认的嵌入式Web服务器)等依赖。

通过在项目中添加Starter依赖,开发者无需逐个添加这些库。Starter通常命名为 spring-boot-starter-xxx,其中xxx表示特定的功能模块。例如:

  • spring-boot-starter-data-jpa:包含Spring Data JPA和Hibernate依赖。
  • spring-boot-starter-security:包含Spring Security依赖。
  • spring-boot-starter-web:用于开发Web应用,包含Spring MVC及相关依赖。

自动装配机制

Spring Boot在启动时会自动扫描类路径下的资源文件和META-INF/spring.factories,从中找到定义的自动配置类。每个自动配置类根据一组条件(如类路径中是否存在特定类或是否有某些配置属性)来决定是否启用。

自动装配机制基于@EnableAutoConfiguration注解,它通过读取所有路径下 spring.factories 文件中的配置,加载与项目相关的自动配置类。例如:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfigure.MyAutoConfiguration

加载配置文件

Spring Boot默认会从classpath下加载application.propertiesapplication.yml配置文件,开发者可以通过这些文件提供自定义的配置属性,来覆盖自动配置中的默认值。此外,Spring Boot还支持通过命令行参数、环境变量等方式注入配置,以便在不同环境中灵活调整应用配置。

自定义Starter

创建工程

创建一个名为 thread-spring-boot-starter 的工程,使用 Maven 添加所需的基本依赖。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.3</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
</dependencies>

编写配置类

创建自动配置类,使用 @Configuration 注解,并结合条件注解(如 @ConditionalOnClass)来定义自动装配逻辑。

@Configuration
public class ThreadPoolAutoConfiguration {

    @Bean
    public ThreadPoolExecutor threadPoolExecutor() {
        return new ThreadPoolExecutor(10, 10, 100,
                TimeUnit.MICROSECONDS, new ArrayBlockingQueue<>(100));
    }
}

注册配置类

src/main/resources/META-INF目录下创建一个spring.factories文件,并添加以下配置:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.example.config.ThreadPoolAutoConfiguration

添加Starter依赖

将其打包并安装到本地Maven仓库。

mvn clean install

一旦安装完成,其他项目就可以通过在其pom.xml文件中添加以下依赖来使用这个Starter了:

<!-- 引入手写的starter-->
<dependency>
    <groupId>org.example</groupId>
    <artifactId>thread-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

使用Starter

在其他项目中,可以通过@Autowired注解获取ThreadPoolExecutor实例。

@SpringBootTest(classes = ProjectApplication.class)
public class StarterTest {
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Test
    public void threadPoolTest() {
        System.out.println("核心线程数:" + threadPoolExecutor.getCorePoolSize());
        System.out.println("最大线程数:" + threadPoolExecutor.getMaximumPoolSize());
    }
}