@Import アノテーション原則のソースコード分析

@Importアノテーションのソースコードの入力場所

ソースコードの入力位置はConfigurationClassParser#doProcessConfigurationClassメソッド内にありますが、なぜこの位置になっているのかというと、まずボタンを押してから穴を埋めて改善します。
Spring は構成クラスをどのように解析するか

Spring が構成クラス (Configuration | config クラス) をどのように処理するかを簡単に見てみましょう。@Import構成クラス内のアノテーションが処理されます。

  1. <1> まず「内部クラス」を処理します
  2. <2> @PropertySource アノテーションの処理
  3. <3> @ComponentScan アノテーションの処理
  4. <4> @Import アノテーションの処理
  5. <5> @ImportResource アノテーションの処理
  6. <6> @Beanアノテーションの処理
  7. <7> インターフェースのデフォルトの処理方法
  8. <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 アノテーションが付けられたクラスのコレクションが完了したため、次のステップは当然、収集されたクラスをどのように扱うかになります。
プロセスは次のとおりです。

  • タイプの場合ImportSelectorselectImportsBean を登録するためにメソッドが呼び出されます。
  • 「はい」の場合ImportBeanDefinitionRegistrarregisterBeanDefinitionsメソッドを呼び出して 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));
    }
}

最後に次のように書きます。

  1. @Import の一般的な機能は、Bean をコンテナーに登録することです。
  2. 上記のプロセス全体は次のように実行されます。構成クラスポストプロセッサこのクラスは、Spring エコシステムの多くのアノテーション(
    など) の原理です。@PropertySource@ComponentScan@Import@Bean
  3. 興味のある学生は原理を学ぶことができますConfigurationClassPostProcessor。ヒント: 多くの再帰が含まれています。絵を描くこともできます。そうしないとめまいがしやすくなります。

ポータル: nanny Spring5 ソース コード分析

著者とテクノロジーや仕事生活について交流することを歓迎します

著者に連絡する

おすすめ

転載: blog.csdn.net/yuchangyuan5237/article/details/128681016