@Importアノテーションの動作と仕組み

@Importアノテーションのパラメータにはクラス名を指定できます。例えば@Import(Abc.class)のように記述します。クラスAbcの型に応じて、Springコンテナは以下の4つの方法で処理を行います:

    1. AbcクラスがImportSelectorインターフェースを実装している場合、SpringコンテナはAbcクラスをインスタンス化し、selectImportsメソッドを呼び出します;
    1. DeferredImportSelectorはImportSelectorのサブインターフェースです。AbcクラスがDeferredImportSelectorインターフェースを実装している場合、SpringコンテナはAbcクラスをインスタンス化し、selectImportsメソッドを呼び出します。ImportSelectorの実装クラスと異なる点は、DeferredImportSelectorの実装クラスのselectImportsメソッドは、より遅いタイミングで呼び出されることです。@Configurationアノテーションに関連するすべての処理が完了した後で呼び出されます(具体的なロジックはConfigurationClassParser.processDeferredImportSelectorsメソッド内にあります)。DeferredImportSelectorとImportSelectorの違いについて詳しく知りたい場合は、「ImportSelectorとDeferredImportSelectorの違い(spring4)」を参照してください;
    1. AbcクラスがImportBeanDefinitionRegistrarインターフェースを実装している場合、SpringコンテナはAbcクラスをインスタンス化し、registerBeanDefinitionsメソッドを呼び出します;
    1. AbcがImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrarのいずれのインターフェースも実装していない場合、SpringコンテナはAbcクラスをインスタンス化します。詳細は公式ドキュメントをご参照ください;

Springソースコードのバージョン:5.0.5.RELEASE

Springコンテナが@Importアノテーションをどのように処理するかを追跡します。コンテナの初期化は通常、AbstractApplicationContextクラスのrefreshメソッドから開始されます。他のプロセスは省略し、関連する部分に直接スタックトレースを示します;

doProcessConfigurationClass:300, ConfigurationClassParser {org.springframework.context.annotation}
processConfigurationClass:245, ConfigurationClassParser {org.springframework.context.annotation}
parse:194, ConfigurationClassParser {org.springframework.context.annotation}
doProcessConfigurationClass:293, 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:316, ConfigurationClassPostProcessor {org.springframework.context.annotation}
postProcessBeanDefinitionRegistry:233, ConfigurationClassPostProcessor {org.springframework.context.annotation}
invokeBeanDefinitionRegistryPostProcessors:273, PostProcessorRegistrationDelegate {org.springframework.context.support}
invokeBeanFactoryPostProcessors:93, PostProcessorRegistrationDelegate {org.springframework.context.support}
invokeBeanFactoryPostProcessors:694, AbstractApplicationContext {org.springframework.context.support}
refresh:532, AbstractApplicationContext {org.springframework.context.support}
ConfigurationClassParserは@PropertySources、@ComponentScan、@Import、@ImportResource、@Beanアノテーションを解析する場所です<br></br>ConfigurationClassParser#parseメソッド内では
    public void parse(Set<BeanDefinitionHolder> configCandidates) {
        //後で実行されるparseメソッド内で、すべてのDeferredImportSelector実装クラスがdeferredImportSelectorsコレクションに追加されます
        this.deferredImportSelectors = new LinkedList<>();

        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                if (bd instanceof AnnotatedBeanDefinition) {
                  //このparseメソッド内で、すべてのDeferredImportSelector実装クラスがdeferredImportSelectorsコレクションに追加されます。そのselectImportsメソッドは実行されず、他のImportSelector実装クラスのselectImportsはすべて実行されます
                    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);
            }
        }

        //このメソッド内で、deferredImportSelectorsコレクション内のすべてのオブジェクトが取り出され、そのselectImportsメソッドが実行されます
        processDeferredImportSelectors();
    }

@importの処理部分を見てみましょう

    protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
            throws IOException {
        ......

        // 任意の@Importアノテーションを処理
        processImports(configClass, sourceClass, getImports(sourceClass), true);

        ......
    }

getImportsの追跡は、クラスの@Importアノテーションを再帰的に取得するもので、取得後にprocessImportsメソッドで処理が行われます:

    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) {
                  //ImportSelectorインターフェースの実装クラスの場合、ここで処理
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // キャンディデートクラスがImportSelector -> そのクラスに委譲してインポートを決定
                        Class<?> candidateClass = candidate.loadClass();
                        // これらのImportSelector実装クラスをインスタンス化
                        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        // この実装クラスがBeanFactoryAware、EnvironmentAwareなどのインターフェースも実装している場合、これらのインターフェースで宣言されたメソッドを先に実行
                        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
                        // この実装クラスがDeferredImportSelectorインターフェースも実装している場合、deferredImportSelectorsに追加され、解析完了後に実行
                        if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectors.add(
                                    new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                        }
                        else {
                          //注意:この行が重要なコードです!!!実装クラスのselectImportsメソッドを実行
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }
                    //ImportBeanDefinitionRegistrarの実装クラスを処理
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // キャンディデートクラスがImportBeanDefinitionRegistrar ->
                        // それに委譲して追加のbean定義を登録
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                registrar, this.environment, this.resourceLoader, this.registry);
                        //configClass.addImportBeanDefinitionRegistrarメソッドはImportBeanDefinitionRegistrar実装クラスをconfigClassのメンバ変数importBeanDefinitionRegistrarsに保存します。
                        //後のConfigurationClassPostProcessorクラスのprocessConfigBeanDefinitionsメソッド内で、parser.parseの処理完了後に
                        //this.reader.loadBeanDefinitions(configClasses)が呼び出され、これらのImportBeanDefinitionRegistrar実装クラスのregisterBeanDefinitionsメソッドが呼び出されます
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    //通常のクラス
                    else {
                        // キャンディデートクラスがImportSelectorまたはImportBeanDefinitionRegistrarではない ->
                        // それを@Configurationクラスとして処理
                        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();
            }
        }
    }

まとめ:

    1. 通常のクラス(ImportBeanDefinitionRegistrar、ImportSelector、DeferredImportSelectorなどのインターフェースを実装していないクラス)は、ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromImportedResourcesメソッドを通じてbean定義がSpringコンテナに登録されます;
    1. ImportSelector実装クラスの場合、そのselectImportsメソッドが返すbeanの名前は、ConfigurationClassParserクラスのasSourceClassメソッドによってSourceClassオブジェクトに変換され、通常のクラスとして処理されます;
    1. ImportSelectorとDeferredImportSelectorの違いは、selectImportsメソッドの実行タイミングにあります。このタイミングの違いの間に、SpringコンテナはこのConfigurationクラスに対して@ImportResource、@Beanなどのアノテーションに関するロジックを処理します(ここでは@Bean修飾されたメソッドの処理のみを指し、すぐに@Bean修飾されたメソッドを呼び出すわけではない点に注意してください!);
    1. ImportBeanDefinitionRegistrar実装クラスのregisterBeanDefinitionsメソッドが呼び出され、ビジネスで必要なbean定義を登録できます;

タグ: Spring Framework Java アノテーション DI Bean定義

6月28日 00:48 投稿