前言

在SpringBoot项目中的主类上标注@SpringBootApplication注解,便可实现基本的自动配置功能。本文通过源码了解其背后的原理。本文中SpringBoot版本号为2.7.5。

@SpringBootApplication的构成

@SpringBootApplication注解是一个复合注解,核心由@SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan三部分构成。接下来我们分别来看三个核心注解的作用

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

@SpringBootConfiguration

Main类本身也是SpringBoot中的配置类,只不过是核心配置类。@SpringBootConfiguration核心其实就是@Configuration

@Configuration	// 核心
public @interface SpringBootConfiguration {
}

@ComponentScan

用于指定扫描的类,Spring中的注解,可以具体看看。此处意义不大,暂且略过

@EnableAutoConfiguration

本次最核心的注解,用于开启自动配置。它也是一个复合注解,包含@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)两个部分。我们接下来详细看两个部分的代码。

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

翻译:自动配置包。我们查看其源码,发现它使用@Import注解为容器导入了Registrar组件。

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

AutoConfigurationPackages.Registrar组件为容器导入了一系列组件

我们这里用到了@Import的高级用法

  1. ImportBeanDefinitionRegistrar接口:允许自动配置类向Spring IoC容器中注册额外的组件
  • 通过@Import注解导入类时,若该类实现了此接口,会调用其registerBeanDefinitions方法
  1. DeterminableImports接口:让Spring Boot在自动配置过程中确定哪些类应该被添加到自动配置类的导入列表中
  • 当SpringBoot需要自动配置某个类时,若该类实现了此接口,会调用其determineImports方法,用于确定哪些类应该被添加到自动配置类的导入列表中
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        /*
        	通过注解元信息,当前获取包名,存到了一个数组里
        	new PackageImports(metadata).getPackageNames().toArray(new String[0]) 
        */
        /*
        	第一个参数是BeanDefinitionRegistry对象,用于向IOC容器中注册组件
        	第二个参数是一个字符串数组,存包的全限定名
        	该方法扫描指定的包,将其中组件注册到IOC容器中 
        */
        register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
    }
	
    // 此处该方法不重要
    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImports(metadata));
    }

}

现在,我们Main类所在包下的所有组件,都被自动导入了

@Import(AutoConfigurationImportSelector.class)

通过@Import注解导入了AutoConfigurationImportSelector类型的Bean。AutoConfigurationImportSelector实现了ImportSelector接口,被用于动态地确定应该导入哪些自动配置类。

// 略去一部分代码
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

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

继续查看getAutoConfigurationEntry方法

// 给容器中批量导入组件
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 = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

getCandidateConfigurations方法获取了所有的候选配置,通过调试可以大概看到

"org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration" "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration" "org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration" "org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration" ...

继续查看getCandidateConfigurations方法。

  1. 会根据传入的AnnotationMetadataAnnotationAttributes参数,使用SpringFactoriesLoader工具类,加载所有在META-INF/spring.factories文件中声明的类,并将它们添加到一个List中。
  2. 还通过ImportCandidates.load()方法,加载了所有在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中声明的AutoConfiguration类,并将它们也添加到了上述的List中。(最新的标准,推荐使用它)

我们简单看看SpringFactoriesLoader.loadFactoryNames的代码实现:

// 略去一部分代码
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 可以看到,是通过SpringFactoriesLoader.loadFactoryNames获取的候选列表
    List<String> configurations = new ArrayList<>(
            SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(),
                getBeanClassLoader()
            )
    );
    ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
    Assert.notEmpty(configurations, "...");
    return configurations;
}
// 略去一部分代码
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    // key: 接口名, value: 实现类
	Map<String, List<String>> result = cache.get(classLoader);
    
    /*
    	从"META-INF/spring.factories"加载文件
    	FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
    */
    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()) {
            
        }
    }

    return result;
}

举例说明:spring-boot-test-autoconfigureMETA-INF/spring.factories文件为以下内容,其中内容为:接口名+实现类名

# DefaultTestExecutionListenersPostProcessors
org.springframework.boot.test.context.DefaultTestExecutionListenersPostProcessor=\
org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener$PostProcessor

# Spring Test ContextCustomizerFactories
org.springframework.test.context.ContextCustomizerFactory=\
org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory,\
org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory,\
org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizerFactory,\
org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizerFactory,\
org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory

# Test Execution Listeners
org.springframework.test.context.TestExecutionListener=\
org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener,\
org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener,\
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener,\
org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener,\
org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener

2.7.0以后,不推荐将配置写入spring.factories中了,推荐写在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

自动配置类会按需开启

虽然上述场景中,所有自动配置启动时默认加载全部(xxxAutoConfiguration),但是按照每个类的装配规则(@Conditional),最终还是会按需配置

例如:AopAutoConfiguration

@AutoConfiguration
// spring.aop.auto = true开启aop
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
    
    // 存在Advice类,
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Advice.class)
	static class AspectJAutoProxyingConfiguration { }

    // 没有Advice类,则开启的简单的aop功能
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
    // spring.aop.proxy-target-class = true
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
	static class ClassProxyingConfiguration { }
}