前言
通常来说,我们接触过的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应用都能正常运行。