前言

通常来说,我们接触过的SpringBoot程序,只需要运行主启动类的main方法就可以启动应用了。

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

所以来研究一下@SpringBootApplication注解。

进入@SpringBootApplication

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

可见,在启动类上标注的注解应该是三个:@Configuration + @EnableAutoConfiguration + @ComponentScan,组合而来的。
我们挨个来看他们都起什么作用:

  • @ComponentScan
    这个注解Spring框架里就有了,它可以指定包扫描的跟路径,让Spring来扫描指定的包及其子包下的组件,如果不指定路径,默认扫描当前配置了所在包及其子包里的所有组件。
  • @SpringBootConfiguration
    它被@Configuration标注,说明它实际上是标注配置类的,而且是标注主启动类的。被它标注的类,会被IOC容器认定为配置类。

一个被@Configuration标注的类,相当于一个applicationContext.xml的配置文件。

  • @EnableAutoConfiguration
    重点来了,SpringBoot的自动装配完全由它来开启,所以下面重点看看它。

@EnableAutoConfiguration

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

可见它也是一个组合注解,分别来看。

@AutoConfigurationPackage

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}

它的实现原理是在注解上标注了@Import,导入了一个AutoConfigurationPackages.Registrar

AutoConfigurationPackages.Registrar

/**
 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
 * configuration.
 */
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, new PackageImport(metadata).getPackageName());
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImport(metadata));
    }

}

很明显,它就是实现把主配置所在根包保存起来以便后期扫描用的。分析源码:
Registrar实现了ImportBeanDefinitionRegistrar接口,它向IOC容器中要手动注册组件。

在重写的registerBeanDefinitions方法中,它要调用外部类AutoConfigurationPackages的register方法。

register方法

private static final String BEAN = AutoConfigurationPackages.class.getName();

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    // 判断 BeanFactory 中是否包含 AutoConfigurationPackages
    if (registry.containsBeanDefinition(BEAN)) {
        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
        ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
        // addBasePackages:添加根包扫描包
        constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
    }
    else {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(BasePackages.class);
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(BEAN, beanDefinition);
    }
}

划重点:它要判断当前IOC容器中是否包含AutoConfigurationPackages。如果有,就会拿到刚才传入的包名,设置到一个 basePackage里,basePackage的意义很明显是根包。

换句话说,它要取主启动类所在包及子包下的组件,用于之后进行的扫描。

@Import(AutoConfigurationImportSelector.class)

它导入了一个ImportSelector,来向容器中导入组件。

AutoConfigurationImportSelector

它的核心部分就是selectImport方法:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }

        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
        // 加载自动配置类
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
            annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

关键的源码在getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata)

getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata)

/**
 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
 * of the importing {@link Configuration @Configuration} class.
 *
 * 根据导入的@Configuration类的AnnotationMetadata返回AutoConfigurationImportSelector.AutoConfigurationEntry。
 */
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
        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, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
}

这个方法里有一个非常关键的集合:configurations(最后直接拿他来返回出去了,给selectImports方法转成 String[])

既然最后拿它返回出去,必然它是导入其他组件的核心。

这个 configurations 集合的数据,都是通过 getCandidateConfigurations 方法来获取:

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

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        // SPI机制加载自动配置类
        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;
}

这个方法又调用了SpringFactoriesLoader.loadFactoryNames方法,传入的Class就是@EnableAutoConfiguration

SpringFactoriesLoader.loadFactoryNames

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

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

源码中使用classLoader去加载了指定常量路径下的资源:FACTORIES_RESOURCE_LOCATION,而这个常量指定的路径实际是:META-INF/spring.factories

这个文件在spring-boot-autoconfiguration包下可以找到。

spring-boot-autoconfiguration包下META-INF/spring.factories节选:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
......

之后拿到这个资源文件,以Properties的形式加载,并取出org.springframework.boot.autoconfigure.EnableAutoConfiguration指定的所有自动配置类(是一个很大的字符串,里面都是自动配置类的全限定类名),
装配到IOC容器中,之后自动配置类就会通过ImportSelector@Import的机制被创建出来, 之后就生效了。

这也就解释了为什么即便没有任何配置文件,SpringBoot的Web应用都能正常运行。

Last modification:October 22, 2021
如果觉得我的文章对你有用,请随意赞赏