@Importアノテーションのソースコードの入力場所
ソースコードの入力位置はConfigurationClassParser#doProcessConfigurationClass
メソッド内にありますが、なぜこの位置になっているのかというと、まずボタンを押してから穴を埋めて改善します。
Spring は構成クラスをどのように解析するか
Spring が構成クラス (Configuration | config クラス) をどのように処理するかを簡単に見てみましょう。@Import
構成クラス内のアノテーションが処理されます。
- <1> まず「内部クラス」を処理します
- <2> @PropertySource アノテーションの処理
- <3> @ComponentScan アノテーションの処理
- <4> @Import アノテーションの処理
- <5> @ImportResource アノテーションの処理
- <6> @Beanアノテーションの処理
- <7> インターフェースのデフォルトの処理方法
- <8> 「親クラスまたは親インタフェース」の扱い
// 这个类的功能如名字所示的就是:完成所有Configuration的解析。至于原理就是递归递归递归.....
class ConfigurationClassParser {
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// Recursively process any member (nested) classes first
// <1> 首先处理 "内部类"
processMemberClasses(configClass, sourceClass);
// Process any @PropertySource annotations
// <2> 处理 @PropertySource 注解
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
} else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// Process any @ComponentScan annotations
// <3> 处理 @ComponentScan 注解
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
// 如果扫描的 definitions,有@Configuration,则递归进行解析
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());
}
}
}
}
// Process any @Import annotations
// <4> 处理 @Import 注解
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations
// <5> 处理 @ImportResource 注解
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// Process individual @Bean methods
// <6> 处理 @Bean 注解
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
// <7> 处理 接口的默认方法
processInterfaces(configClass, sourceClass);
// Process superclass, if any
// <8> 处理 "父类或父接口"
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
return null;
}
}
@Import アノテーションの原則
上記の続きで、@Import ソース コード分析セクションに進みました。
// 代码位置:ConfigurationClassParser#doProcessConfigurationClass
processImports(configClass, sourceClass, getImports(sourceClass), true);
@Import アノテーションを収集する
// 代码位置:ConfigurationClassParser.class
class ConfigurationClassParser {
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
}
// 代码位置:ConfigurationClassParser.class
class ConfigurationClassParser {
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
// 如果 sourceClass 注解了 @Import,则进行递归
if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
// 把@Import注解的value值添加进 imports,完成@Import注解的收集
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
}
収集されたインポートを処理する
前のステップで @Import アノテーションが付けられたクラスのコレクションが完了したため、次のステップは当然、収集されたクラスをどのように扱うかになります。
プロセスは次のとおりです。
- タイプの場合
ImportSelector
、selectImports
Bean を登録するためにメソッドが呼び出されます。 - 「はい」の場合
ImportBeanDefinitionRegistrar
、registerBeanDefinitions
メソッドを呼び出して Bean を登録します - それ以外の場合は
@Configuration
「処理」を押してください
// 代码位置:ConfigurationClassParser#processImports 方法
for (SourceClass candidate : importCandidates) {
// 1、如果是ImportSelector类型则
if (candidate.isAssignable(ImportSelector.class)) {
// 略......
}
// 2、如果是ImportSelector类型则
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// 略......
}
// 3、如果不是以上2种类型,则吧class当做@Configuration来处理
else {
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
最後に次のように書きます。
- @Import の一般的な機能は、Bean をコンテナーに登録することです。
- 上記のプロセス全体は次のように実行されます。構成クラスポストプロセッサこのクラスは、Spring エコシステムの多くのアノテーション( 、、、
など) の原理です。@PropertySource
@ComponentScan
@Import
@Bean
- 興味のある学生は原理を学ぶことができます
ConfigurationClassPostProcessor
。ヒント: 多くの再帰が含まれています。絵を描くこともできます。そうしないとめまいがしやすくなります。
ポータル: nanny Spring5 ソース コード分析
著者とテクノロジーや仕事生活について交流することを歓迎します