一、注解
@SpringBootApplication 注解
@SpringBootApplication 注解實際上是一個組合注解唉锌,它由三個注解組合而成,分別是 @SpringBootConfiguration弄砍、@EnableAutoConfiguration 和 @ComponentScan
@ComponentScan 注解
@ComponentScan 注解不是 Spring Boot 引入的新注解披坏,而是屬于 Spring 容器管理的內(nèi)容。@ComponentScan 注解就是掃描基于 @Component 等注解所標注的類所在包下的所有需要注入的類馏慨,并把相關 Bean 定義批量加載到容器中埂淮。
@ComponentScan注解的處理邏輯在Configuration解析時的ConfigurationClassParser中進行處理
#調(diào)用棧
doProcessConfigurationClass:281, ConfigurationClassParser (org.springframework.context.annotation)
processConfigurationClass:245, ConfigurationClassParser (org.springframework.context.annotation)
parse:202, ConfigurationClassParser (org.springframework.context.annotation)
parse:170, ConfigurationClassParser (org.springframework.context.annotation)
processConfigBeanDefinitions:331, ConfigurationClassPostProcessor (org.springframework.context.annotation)
postProcessBeanDefinitionRegistry:233, ConfigurationClassPostProcessor (org.springframework.context.annotation)
invokeBeanDefinitionRegistryPostProcessors:303, PostProcessorRegistrationDelegate (org.springframework.context.support)
invokeBeanFactoryPostProcessors:106, PostProcessorRegistrationDelegate (org.springframework.context.support)
invokeBeanFactoryPostProcessors:739, AbstractApplicationContext (org.springframework.context.support)
refresh:536, AbstractApplicationContext (org.springframework.context.support)
// @ComponentScan注解處理
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
......
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
......
}
使用ClassPathBeanDefinitionScanner掃描basePackage下的@Component注解類,遞歸解析成BeanDefinition并通過 BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry) 添加到 DefaultListableBeanFactory - beanDefinitionMap 中(這個時候bean并沒有進行實例化写隶,而是進行了注冊倔撞。具體的實例化在finishBeanFactoryInitialization方法中執(zhí)行)。
@SpringBootConfiguration 注解
@SpringBootConfiguration 注解比較簡單慕趴,只是使用了 Spring 中的 @Configuration 注解痪蝇。@Configuration 注解比較常見,提供了 JavaConfig 配置類實現(xiàn)秩贰。具體實現(xiàn)在 ConfigurationClassParser#parse 中霹俺,涉及對 @PropertySources、@ComponentScan毒费、@Import丙唧、@ImportResource、@Bean觅玻、@DeferredImportSelector 注解的處理想际。
@EnableAutoConfiguration 注解
@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 {};
}
這里我們關注兩個新注解,@AutoConfigurationPackage 和 @Import(AutoConfigurationImportSelector.class)
@AutoConfigurationPackage 注解
@AutoConfigurationPackage 注解主要就是引用@Import(AutoConfigurationPackages.Registrar.class)
@Import 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
在 @Import 注解的屬性中可以設置需要引入的類名溪厘,例如 @AutoConfigurationPackage 注解上的 @Import(AutoConfigurationPackages.Registrar.class)胡本。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
根據(jù)該類的不同類型,Spring 容器針對 @Import 注解有以下四種處理方式:
- 如果該類實現(xiàn)了 ImportSelector 接口畸悬,調(diào)用其 selectImports 方法侧甫,返回spring.factories 中配置的類,然后遞歸調(diào)用 processImports 進行處理,最終會被保存在 configurationClasses 中披粟;
- 如果該類實現(xiàn)了 DeferredImportSelector 接口咒锻,則 Spring 容器也會實例化該類并調(diào)用其 selectImports方法。DeferredImportSelector 繼承了 ImportSelector守屉,區(qū)別在于DeferredImportSelector 實例先保存在 deferredImportSelectors 中惑艇,要等到 @Configuration 注解中相關的業(yè)務全部都處理完了才會通過 processDeferredImportSelectors 調(diào)用 selectImports 方法;
- 如果該類實現(xiàn)了 ImportBeanDefinitionRegistrar 接口拇泛,會把Metadata 保存importBeanDefinitionRegistrars 中滨巴,在解析完Configuration 后通過 this.reader.loadBeanDefinitions(configClasses),最后在loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()) 中調(diào)用其 registerBeanDefinitions 方法俺叭;
- 如果該類沒有實現(xiàn)上述三種接口中的任何一個恭取,把這個類當成是@Configuration注解修飾的類遞歸重頭開始解析這個類;
關于注解就到這里绪颖,下面重點分析下 @Import 的兩個類 AutoConfigurationPackages.Registrar 和 AutoConfigurationImportSelector
二秽荤、AutoConfigurationPackages.Registrar
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 類實現(xiàn)了前面第三種情況中提到的 ImportBeanDefinitionRegistrar 接口并重寫了 registerBeanDefinitions 方法,該方法中調(diào)用 AutoConfigurationPackages 自身的 register 方法:
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition
.getConstructorArgumentValues();
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);
}
}
這個方法的邏輯是先判斷整個 Bean 有沒有被注冊柠横,如果已經(jīng)注冊則獲取 Bean 的定義窃款,通過 Bean 獲取構造函數(shù)的參數(shù)并添加參數(shù)值;如果沒有牍氛,則創(chuàng)建一個新的 Bean 的定義晨继,設置 Bean 的類型為 AutoConfigurationPackages 類型并進行 Bean 的注冊,最終保存在 DefaultListableBeanFactory 的 beanDefinitionMap / beanDefinitionNames中搬俊。
三紊扬、AutoConfigurationImportSelector
AutoConfigurationImportSelector 類實現(xiàn)了 @Import 注解第二種情況中的 DeferredImportSelector 接口,所以會執(zhí)行如下所示的 selectImports 方法:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//獲取 configurations 集合
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 StringUtils.toStringArray(configurations);
}
這段代碼的核心是通過 getCandidateConfigurations 方法獲取 configurations 集合并進行過濾唉擂。getCandidateConfigurations 方法如下所示:
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;
}
AutoConfigurationImportSelector 所依賴的最關鍵組件就是 SpringFactoriesLoader餐屎。SpringFactoriesLoader 以服務接口命名的文件是放在 META-INF/spring.factories 文件夾下,對應的 Key 為 EnableAutoConfiguration玩祟。SpringFactoriesLoader 會查找所有 META-INF/spring.factories 文件夾中的配置文件腹缩,并把 Key 為 EnableAutoConfiguration 所對應的配置項通過反射實例化為配置類并加載到容器中。
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);
}
}
以下就是 spring-boot-autoconfigure 工程中所使用的 spring.factories 配置文件片段空扎,可以看到 EnableAutoConfiguration 項中包含了各式各樣的配置項藏鹊,這些配置項在 Spring Boot 啟動過程中都能夠通過 SpringFactoriesLoader 加載到運行時環(huán)境,從而實現(xiàn)自動化配置:
# 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.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
…
以上就是 Spring Boot 中基于 @SpringBootApplication 注解實現(xiàn)自動配置的基本過程和原理转锈。當然盘寡,@SpringBootApplication 注解也可以基于外部配置文件加載配置信息〈榭基于約定優(yōu)于配置思想竿痰,Spring Boot 在加載外部配置文件的過程中大量使用了默認配置脆粥。
---------over---------