1. 程式人生 > >Springboot - @Import 詳解

Springboot - @Import 詳解

今天抽空在仔細看了下Springboot 關於 @Import 的處理過程, 記下來以後看。

1. @Import

先看Spring對它的註釋 (文件貼過來的), 總結下來作用就是和xml配置的 <import />標籤作用一樣,允許通過它引入 @Configuration 註解的類 (java config), 引入ImportSelector介面(這個比較重要, 因為要通過它去判定要引入哪些@Configuration) 和 ImportBeanDefinitionRegistrar 介面的實現, 也包括 @Component註解的普通類。

但是如果要引入另一個xml 檔案形式配置的 bean, 則需要通過 @ImportResource 註解。

/**
 * Indicates one or more {@link Configuration @Configuration} classes to import.
 *
 * <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
 * Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
 * {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
 * classes (as of 4.2; analogous to {
@link AnnotationConfigApplicationContext#register}). * * <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be * accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired} * injection. Either the bean itself can be autowired, or the configuration class instance * declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly * navigation between {
@code @Configuration} class methods. * * <p>May be declared at the class level or as a meta-annotation. * * <p>If XML or other non-{@code @Configuration} bean definition resources need to be * imported, use the {@link ImportResource @ImportResource} annotation instead. * * @author Chris Beams * @author Juergen Hoeller * @since 3.0 * @see Configuration * @see ImportSelector * @see ImportResource */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { /** * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar} * or regular component classes to import. */ Class<?>[] value(); }
View Code

2. ImportSelector

因為 @Import 的實現有很多時候需要藉助 ImportSelector 介面, 所以我們再看下這個介面的描述, 總結下來就是需要通過這個介面的實現去決定要引入哪些 @Configuration。 它如果實現了以下四個Aware 介面, 那麼spring保證會在呼叫它之前先呼叫Aware介面的方法。

至於為什麼要保證呼叫Aware, 我個人覺得應該是你可以通過這些Aware去感知系統裡邊所有的環境變數, 從而決定你具體的選擇邏輯。

/**
 * Interface to be implemented by types that determine which @{@link Configuration}
 * class(es) should be imported based on a given selection criteria, usually one or more
 * annotation attributes.
 *
 * <p>An {@link ImportSelector} may implement any of the following
 * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
 * methods will be called prior to {@link #selectImports}:
 * <ul>
 * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li>
 * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
 * </ul>
 *
 * <p>ImportSelectors are usually processed in the same way as regular {@code @Import}
 * annotations, however, it is also possible to defer selection of imports until all
 * {@code @Configuration} classes have been processed (see {@link DeferredImportSelector}
 * for details).
 *
 * @author Chris Beams
 * @since 3.1
 * @see DeferredImportSelector
 * @see Import
 * @see ImportBeanDefinitionRegistrar
 * @see Configuration
 */
public interface ImportSelector {

    /**
     * Select and return the names of which class(es) should be imported based on
     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);

}
ImportSelector

3. Springboot 對@Import註解的處理過程

Springboot對註解的處理都發生在AbstractApplicationContext -> refresh() -> invokeBeanFactoryPostProcessors(beanFactory) -> ConfigurationClassPostProcessor -> postProcessBeanDefinitionRegistry()方法中。 

(稍微說下也免得我自己忘了, springboot初始化的普通context(非web) 是AnnotationConfigApplicationContext, 在初始化的時候會初始化兩個工具類, AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner 分別用來從 annotation driven 的配置和xml的配置中讀取beanDefinition並向context註冊, 那麼在初始化 AnnotatedBeanDefinitionReader 的時候, 會向BeanFactory註冊一個ConfigurationClassPostProcessor 用來處理所有的基於annotation的bean, 這個ConfigurationClassPostProcessor 是 BeanFactoryPostProcessor 的一個實現,springboot會保證在  invokeBeanFactoryPostProcessors(beanFactory) 方法中呼叫註冊到它上邊的所有的BeanFactoryPostProcessor)

如下程式碼顯示是通過 ConfigurationClassParser 類來轉換的

// Parse each @Configuration class
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment,
                this.resourceLoader, this.componentScanBeanNameGenerator, registry);

那麼在 ConfigurationClassParser -> processConfigurationClass() -> doProcessConfigurationClass() 方法中我們找到了 (這裡邊的流程還是很清楚的, 分別按次序處理了@PropertySource, @ComponentScan, @Import, @ImportResource, 在處理這些註解的時候是通過遞迴處理來保證所有的都被處理了)

// Process any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), true);

那接下來就看它到底是怎麼做的 . 流程依然清晰 :

  首先, 判斷如果被import的是 ImportSelector.class 介面的實現, 那麼初始化這個被Import的類, 然後呼叫它的selectImports方法去獲得所需要的引入的configuration, 然後遞迴處理

  其次, 判斷如果被import的是 ImportBeanDefinitionRegistrar 介面的實現, 那麼初始化後將對當前物件的處理委託給這個ImportBeanDefinitionRegistrar (不是特別明白, 只是我的猜測)

  最後, 將import引入的類作為一個正常的類來處理 ( 呼叫最外層的doProcessConfigurationClass())

所以, 從這裡我們知道, 如果你引入的是一個正常的component, 那麼會作為@Compoent或者@Configuration來處理, 這樣在BeanFactory裡邊可以通過getBean拿到, 但如果你是 ImportSelector 或者 ImportBeanDefinitionRegistrar 介面的實現, 那麼spring並不會將他們註冊到beanFactory中,而只是呼叫他們的方法。

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();
            }
        }
    }