@Importアノテーション
@Importは、Javaアノテーション構成に基づくSpringの主要コンポーネントです。@Importアノテーションは、@ Beanアノテーションの機能と、xml構成ファイルの<import>タグに基づいて複数の分散xmlファイルを編成する元のSpring関数を提供します。もちろん、ここでは複数の分散@Configurationクラスを編成します。 。
@Importアノテーションの機能を以下に説明します。
1.他の@Configurationを導入します
次のインターフェイスと2つの実装クラスがあるとします。
package com.test
interface ServiceInterface {
void test();
}
class ServiceA implements ServiceInterface {
@Override
public void test() {
System.out.println("ServiceA");
}
}
class ServiceB implements ServiceInterface {
@Override
public void test() {
System.out.println("ServiceB");
}
}
复制代码
2つの@ Configuration、ConfigA、@ Import、ConfigB:
package com.test
@Import(ConfigB.class)
@Configuration
class ConfigA {
@Bean
@ConditionalOnMissingBean
public ServiceInterface getServiceA() {
return new ServiceA();
}
}
@Configuration
class ConfigB {
@Bean
@ConditionalOnMissingBean
public ServiceInterface getServiceB() {
return new ServiceB();
}
}
复制代码
ConfigAを介してAnnotationConfigApplicationContextを作成し、ServiceInterfaceを取得して、次の実装を確認します。
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigA.class);
ServiceInterface bean = ctx.getBean(ServiceInterface.class);
bean.test();
}
复制代码
出力は次のとおりです。ServiceB。@ Importがそれ自体のクラス定義のロードよりも優先されることを証明します。
2.他のタイプのBeanを直接初期化します
Spring 4.2以降、@ Importはエンティティクラスを直接指定し、このクラス定義をコンテキストにロードできます。たとえば、上記のコードのConfigAの@Importを@Import(ServiceB.class)に変更すると、ServiceBのBeanがコンテナコンテキストに生成され、mainメソッドが実行され、出力は次のようになります。 :ServiceB。@ Importがそれ自体のクラス定義のロードよりも優先されることを証明します。
3.パーソナライズされたロード用にImportSelector(およびDefferredServiceImportSelector)を実装するクラスを指定します
ImportSelectorを実装するクラスを指定し、AnnotationMetadataの属性を介してクラスを動的にロードします。AnnotationMetadataは、Importアノテーションが配置されているクラスの属性です(クラスがアノテーションクラスの場合、このアノテーションクラスが適用される非アノテーションクラスに拡張されます)。
ロードする@Configuationまたは特定のBeanクラスの完全修飾名のString配列を返すには、selectImportsメソッドを実装する必要があります。
package com.test;
class ServiceImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//可以是@Configuration注解修饰的类,也可以是具体的Bean类的全限定名称
return new String[]{"com.test.ConfigB"};
}
}
@Import(ServiceImportSelector.class)
@Configuration
class ConfigA {
@Bean
@ConditionalOnMissingBean
public ServiceInterface getServiceA() {
return new ServiceA();
}
}
复制代码
mainメソッドを再度実行し、ServiceBを出力します。@ Importがそれ自体のクラス定義のロードよりも優先されることを証明します。一般に、フレームワークがAnnotationMetadataのパラメーターに基づいてクラスの動的ロードを実装する場合、通常、組み合わせて使用するために追加のEnableアノテーションが作成されます。例えば:
package com.test;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(ServiceImportSelector.class)
@interface EnableService {
String name();
}
class ServiceImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//这里的importingClassMetadata针对的是使用@EnableService的非注解类
//因为`AnnotationMetadata`是`Import`注解所在的类属性,如果所在类是注解类,则延伸至应用这个注解类的非注解类为止
Map<String , Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
String name = (String) map.get("name");
if (Objects.equals(name, "B")) {
return new String[]{"com.test.ConfigB"};
}
return new String[0];
}
}
复制代码
その後、ConfigAにアノテーション@EnableService(name = "B")を追加します
package com.test;
@EnableService(name = "B")
@Configuration
class ConfigA {
@Bean
@ConditionalOnMissingBean
public ServiceInterface getServiceA() {
return new ServiceA();
}
}
复制代码
mainメソッドを再度実行し、ServiceBを出力します。
また、DeferredImportSelectorインターフェースを実装して、selectImportsによって返されるクラスが、@ Importアノテーションのように最初にロードされるのではなく、すべて最後にロードされるようにすることもできます。例えば:
package com.test;
class DefferredServiceImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
String name = (String) map.get("name");
if (Objects.equals(name, "B")) {
return new String[]{"com.test.ConfigB"};
}
return new String[0];
}
}
复制代码
EnableServiceアノテーションを変更します。
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(DefferredServiceImportSelector.class)
@interface EnableService {
String name();
}
复制代码
このようにして、ConfigAは、DefferredServiceImportSelectorによって返されるConfigBの前にロードされ、mainメソッドを実行して、次のように出力します。ServiceA
4.パーソナライズされたロード用にImportBeanDefinitionRegistrarを実装するクラスを指定します
使用法と目的はImportSelectorに似ていますが、プロパティの動的な挿入、Beanのタイプとスコープの変更など、Beanを再定義する場合は、ImportBeanDefinitionRegistrarを実装するクラスを指定する必要があります。例えば:
ServiceCを定義する
package com.test;
class ServiceC implements ServiceInterface {
private final String name;
ServiceC(String name) {
this.name = name;
}
@Override
public void test() {
System.out.println(name);
}
}
复制代码
ServiceImportBeanDefinitionRegistrarを定義して、ServiceCを動的に登録し、EnableServiceを変更します
package com.test;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(ServiceImportBeanDefinitionRegistrar.class)
@interface EnableService {
String name();
}
class ServiceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
String name = (String) map.get("name");
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(ServiceC.class)
//增加构造参数
.addConstructorArgValue(name);
//注册Bean
registry.registerBeanDefinition("serviceC", beanDefinitionBuilder.getBeanDefinition());
}
}
复制代码
後のソースコード分析によると、ImportBeanDefinitionRegistrarが@Beanアノテーションの後にロードされることがわかるので、ConfigAを変更して@ConditionalOnMissingBeanアノテーションが付けられたBeanを削除します。そうしないと、ConfigAのServiceInterfaceが生成されます。
package com.test;
@EnableService(name = "TestServiceC")
@Configuration
class ConfigA {
// @Bean
// @ConditionalOnMissingBean
// public ServiceInterface getServiceA() {
// return new ServiceA();
// }
}
复制代码
mainを実行した後、出力:TestServiceC
@インポート関連のソースコード分析
BeanFactoryPostProcessorによって処理されるときに、@ Importアノテーションをロードして解析します。
AbstractApplicationContextの更新メソッド
-> invokeBeanFactoryPostProcessors(beanFactory);
-> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory、getBeanFactoryPostProcessors());
-> RegistryProcessor.postProcessBeanDefinitionRegistry(registry);
ここでのregistryProcessorは、ConfigurationClassPostProcessorを参照します。
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(registry)
-> processConfigBeanDefinitions(registry):
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
//省略一些配置检查与设置的逻辑
//根据@Order注解,排序所有的@Configuration类
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// 创建ConfigurationClassParser解析@Configuration类
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
//剩余没有解析的@Configuration类
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
//已经解析的@Configuration类
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
//解析
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// 生成类定义读取器读取类定义
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
//省略检查是否有其他需要加载的配置的逻辑
}
}
while (!candidates.isEmpty());
//省略后续清理逻辑
}
复制代码
その中で、parser.parse(candidates)のロジックは、主にorg.springframework.context.annotation.ConfigurationClassParserによって実装され、関数は@Importアノテーションと@Importアノテーションをロードすることです。reader.loadBeanDefinitions(configClasses);のロジックは、主にorg.springframework.context.annotation.ConfigurationClassBeanDefinitionReaderのloadBeanDefinitionsForConfigurationClassメソッドによって実装されます。この関数は、上記で解析された構成をBean定義であるBeanDefinitionに変換します。
1. @ Importアノテーションをロードします
org.springframework.context.annotation.ConfigurationClassParser
1つ目は解析メソッドです
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
//这里的parse实际上就是调用下面即将分析的doProcessConfigurationClass
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
//最后处理所有的`DeferredImportSelector`,符合上面提到的`DeferredImportSelector`的功能
this.deferredImportSelectorHandler.process();
}
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
//处理`@Component`注解的MemberClass相关代码...
//处理`@PropertySource`注解相关代码...
//处理`@ComponentScan`注解相关代码...
//处理`@Import`注解:
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
//处理`@ImportResource`注解相关代码...
//处理`@Bean`注解相关代码...
//处理接口方法相关代码...
//处理父类相关代码...
}
复制代码
getImportsメソッドを使用して関連する@Importクラスを収集します。
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
//递归查询所有注解以及注解的注解是否包含@Import
collectImports(sourceClass, imports, visited);
return imports;
}
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();
//对于非@Import注解,递归查找其内部是否包含@Import注解
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
//添加@Import注解里面的所有配置类
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
复制代码
収集後、分析することができます。
2. @ Importアノテーションを解析します
分析方法は次のとおりです。processImports
//在解析时,入栈,解析结束后,出栈,通过检查栈中是否有当前类,判断是否有循环依赖
private final ImportStack importStack = new ImportStack();
//记录所有的ImportBeanDefinitionRegistrar
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>();
//解析也是递归方法
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
//通过importStack检查循环依赖
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)) {
//处理ImportSelector接口的实现类
Class<?> candidateClass = candidate.loadClass();
//创建这些Selector实例
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
//查看是否有过滤器
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
//如果是DeferredImportSelector,则用deferredImportSelectorHandler处理
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
//如果不是DeferredImportSelector,调用selectImports方法获取要加载的类全限定名称,递归调用本方法继续解析
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// 处理ImportBeanDefinitionRegistrar接口的实现类
Class<?> candidateClass = candidate.loadClass();
//同样的,创建这些ImportBeanDefinitionRegistrar实例
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
//放入importBeanDefinitionRegistrar,用于后面加载
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
//处理@Configuration注解类,或者是普通类(直接生成Bean)
//在栈加上这个类
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
//递归回到doProcessConfigurationClass处理@Configuration注解类 processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
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();
}
}
}
复制代码
このようにして、@ Conditionalクラスに関連するすべての@Importアノテーションがロードおよび解析されます。これは、大規模な再帰プロセスです。
3. BeanDefinitionに変換し、コンテナーに登録します
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader的loadBeanDefinitionsForConfigurationClassメソッド:
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
//对Import完成的,加载其Import的BeanDefinition
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
//加载@Bean注解的方法生成的Bean的Definition
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
//@ImportResource 注解加载的
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
//加载ImportBeanDefinitionRegistrar加载的Bean的Definition
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
复制代码
ここから、@ Beanアノテーションが付けられたBeanが、ImportBeanDefinitionRegistrarによって返されるBeanよりも優先してロードされる理由がわかります。
元のリンク:https://juejin.cn/post/6917771718433964040
この記事がお役に立てば幸いです。私の公式アカウントをフォローし、キーワード[インタビュー]に返信して、Javaのコアナレッジポイントとインタビューギフトパッケージをまとめてください。共有する技術的な乾物の記事や関連資料がもっとあります。みんなが一緒に学び、進歩できるようにしましょう!