开发易忽视问题:SpringBootApplication注解实现原理

612 阅读5分钟

@SpringBootApplication 是 Spring Boot 提供的一个便捷注解,用于简化配置和启动 Spring Boot 应用。它实际上是一个组合注解,包含了几个关键的注解。以下是对 @SpringBootApplication 及其底层实现的详细解析。

1. @SpringBootApplication 注解

@SpringBootApplication 本质上是一个组合注解,它包含了以下三个核心注解:

  • @EnableAutoConfiguration
  • @ComponentScan
  • @Configuration
@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 {};
}

2. @SpringBootConfiguration

@SpringBootConfiguration 是一个特殊的 @Configuration 注解,用于表明这是一个 Spring Boot 配置类。

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

3. @EnableAutoConfiguration

@EnableAutoConfiguration 告诉 Spring Boot 根据项目中的依赖自动配置 Spring 应用上下文。这是 Spring Boot 能够简化配置的重要原因。

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

    @AliasFor("exclude")
    Class<?>[] exclude() default {};

    @AliasFor("excludeName")
    String[] excludeName() default {};
}

@EnableAutoConfiguration 使用 @Import(AutoConfigurationImportSelector.class) 导入了 AutoConfigurationImportSelector 类,该类负责从类路径下查找自动配置类并导入它们。

AutoConfigurationImportSelector.java

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, EnvironmentAware, Ordered {
    // Implementation details

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        // Resolve and load auto configuration classes
    }

    // Other methods and implementation details
}

4. @ComponentScan

@ComponentScan 扫描指定包及其子包中的组件(如 @Component@Service@Repository@Controller),并将其注册到 Spring 容器中。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(ComponentScans.class)
@Inherited
public @interface ComponentScan {

    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    // Other attributes and filters
}

总结

@SpringBootApplication 通过组合多个注解简化了 Spring Boot 应用的配置和启动过程:

  • @SpringBootConfiguration:标识配置类。
  • @EnableAutoConfiguration:启用自动配置机制,根据类路径下的依赖自动配置 Spring 应用上下文。
  • @ComponentScan:自动扫描指定包及其子包中的组件,并将其注册到 Spring 容器中。

AutoConfigurationImportSelector实现流程

AutoConfigurationImportSelector 负责在应用启动时自动加载和注册符合条件的自动配置类。通过 @EnableAutoConfiguration 注解,我们可以启用自动配置功能,而 AutoConfigurationImportSelector 这个类具体实现了自动配置类的选择和导入过程。

以下是 AutoConfigurationImportSelector 的详细实现流程:

1. @EnableAutoConfiguration 注解

@EnableAutoConfiguration 注解中使用了 @Import(AutoConfigurationImportSelector.class) 来导入 AutoConfigurationImportSelector 类:

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

2. selectImports 方法

AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口,该接口定义了 selectImports 方法,用于返回需要导入的配置类名列表。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, EnvironmentAware, Ordered {

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        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);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

    // 其他方法及实现细节
}

3. getCandidateConfigurations 方法

getCandidateConfigurations 方法从 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;
}

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

SpringFactoriesLoader.loadFactoryNames 方法会从 META-INF/spring.factories 文件中读取 EnableAutoConfiguration 对应的配置类名列表。

4. 过滤与排除

获取到候选配置类后,AutoConfigurationImportSelector 会进行过滤和排除不需要的配置类:

  • 去重处理:调用 removeDuplicates 方法。
  • 排除指定类:调用 getExclusions 和 checkExcludedClasses 方法,根据注解配置排除某些类。
  • 条件过滤:调用 filter 方法,根据环境和条件进行过滤。

5. 发布事件

在最终确定要导入的配置类列表后,会发布 AutoConfigurationImportEvent 事件,通知相关监听器:

protected void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
    List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
    if (!listeners.isEmpty()) {
        AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
        for (AutoConfigurationImportListener listener : listeners) {
            listener.onAutoConfigurationImportEvent(event);
        }
    }
}

6. 总结

AutoConfigurationImportSelector 实现了自动配置类的选择和导入过程,具体步骤如下:

  1. 读取元数据:读取 EnableAutoConfiguration 注解的属性。
  2. 查找候选类:从 META-INF/spring.factories 文件中读取所有候选的自动配置类。
  3. 去重和排除:对候选配置类进行去重、排除等处理。
  4. 条件过滤:根据环境和条件过滤不适用的配置类。
  5. 返回结果:返回最终确定的自动配置类列表,供 Spring 容器导入

SpringFactoriesLoader.loadFactoryNames如何读取配置类名

SpringFactoriesLoader 是 Spring 框架中的一个辅助类,用于从 META-INF/spring.factories 文件中加载工厂类名。这个文件中通常包含一些关键组件或配置类的映射关系。了解 SpringFactoriesLoader.loadFactoryNames 的实现有助于理解 Spring Boot 自动配置机制的底层原理。

SpringFactoriesLoader 类

首先,我们来看一下 SpringFactoriesLoader 类的核心方法 loadFactoryNames

public abstract class SpringFactoriesLoader {

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

    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentHashMap<>();

    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) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        MultiValueMap<String, String> result = cache.get(classLoaderToUse);
        if (result != null) {
            return result;
        }

        try {
            Enumeration<URL> urls = classLoaderToUse.getResources(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 factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoaderToUse, result);
            return result;
        } catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
}

实现步骤详细解析

  1. 常量定义

    • FACTORIES_RESOURCE_LOCATION 定义了默认的位置 META-INF/spring.factories,用来存放配置文件。
  2. 缓存机制

    • 使用 cache 以 ClassLoader 为键进行缓存,避免重复读取和解析配置文件,提高性能。
  3. 加载工厂类名

    • loadFactoryNames 方法根据传入的 factoryClass 类型,返回对应的工厂类名列表。
  4. 加载 Spring 工厂

    • loadSpringFactories 方法用于加载并解析 spring.factories 配置文件中的内容。
  5. 获取资源

    • classLoaderToUse.getResources(FACTORIES_RESOURCE_LOCATION) 获取所有的 META-INF/spring.factories 文件(可能存在多个)。
  6. 读取和解析资源

    • 遍历所有找到的 URL 资源,通过 PropertiesLoaderUtils.loadProperties 将其加载为 Properties 对象。
    • 遍历 Properties 中的每一条记录,将 key 和 value 作为工厂类名和对应的实现类名。
  7. 结果存储

    • 将解析后的结果存储在 MultiValueMap 中,并缓存到内存中。

示例文件 META-INF/spring.factories

典型的 META-INF/spring.factories 文件内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
...

此文件表示 EnableAutoConfiguration 对应了一系列自动配置类。

加载过程简化步骤

  1. 调用 loadFactoryNames 方法,传入 EnableAutoConfiguration.class 和 ClassLoader
  2. 检查缓存,如果缓存中已有则直接返回结果。
  3. 获取资源,找到所有路径下的 META-INF/spring.factories 文件。
  4. 解析文件,将文件内容加载为 Properties 对象并遍历每个键值对。
  5. 存储结果,将解析结果存储到 MultiValueMap 并缓存。
  6. 返回结果,根据传入的工厂类名查找并返回对应的实现类