1. 程式人生 > >Spring中@Import的各種用法以及ImportAware介面

Spring中@Import的各種用法以及ImportAware介面

@Import 註解

@Import註解提供了和XML中<import/>元素等價的功能,實現匯入的一個或多個配置類。@Import即可以在類上使用,也可以作為元註解使用。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

    /**
     * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
     * or regular component classes to import.
     */
    Class<?>[] value();

}

註解中只有一個value();。支援匯入@Configuration標註的配置類,實現ImportSelector介面的類、實現ImportBeanDefinitionRegistrar介面的類和普通的@component類。

作為元註解使用

@Import可以作為元註解使用,可以在@Import的繼承上封裝一層。我的理解是,這樣做不會對外(使用方)暴露我內部的具體實現細節。

舉個例子:例如@EnableAspectJAutoProxy註解。

@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

@EnableAspectJAutoProxy就是被@Import這個元註解所標誌了,我們(程式設計師)通過使用@EnableAspectJAutoProxy來開啟AspectJAutoProxy,而Spring底層是通過@Import匯入相應的配置類來實現的。

匯入實現ImportSelector介面的類

先來看一下ImportSelector介面,該介面中只有一個方法:

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}

ImportSelector,輸入選擇器。該介面就是用來根據給定的條件,選擇匯入哪些配置類。

舉個例子:例如@EnableTransactionManagement註解。

@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

@EnableTransactionManagement註解中使用了@Import(TransactionManagementConfigurationSelector.class)註解,其中TransactionManagementConfigurationSelector類就是實現了ImportSelector介面。

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
    @Override
    protected String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                return new String[] {AutoProxyRegistrar.class.getName(),
                        ProxyTransactionManagementConfiguration.class.getName()};
            case ASPECTJ:
                return new String[] {
                        TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};
            default:
                return null;
        }
    }
}

方法的內部實現邏輯也很簡單,就是根據不同的AdviceMode匯入不同的配置類,來實現事務管理。

匯入實現ImportBeanDefinitionRegistrar介面的類

ImportBeanDefinitionRegistrar介面中也只有一個方法:

public interface ImportBeanDefinitionRegistrar {
    void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}

該介面允許我們根據所給的註解元資料,按需註冊額外的BeanDefinition

舉個例子:例如@EnableAspectJAutoProxy註解。

@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

@EnableAspectJAutoProxy註解引入了AspectJAutoProxyRegistrar.class類,這個類就是實現了ImportBeanDefinitionRegistrar介面。

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }
}

registerBeanDefinitions中呼叫了AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);方法,這個方法就是在往傳入的BeanDefinitionRegistry registry中註冊BeanDefinition。註冊了BeanDefinition之後,Spring就會去例項化這個Bean,從而達到AspectJAutoProxy作用。

匯入@Configuration類

這次@Import最常見是使用方法。我們可以拆分配置類,然後在程式中按需匯入相應的配置。

舉個例子:例如@EnableRetry註解。使用這個註解可以開啟retry功能。

@EnableAspectJAutoProxy(proxyTargetClass = false)
@Import(RetryConfiguration.class)
public @interface EnableRetry {

其內部就是匯入了RetryConfiguration這個配置類。

ImportAware介面

ImportAware介面是需要和@Import一起使用的。在@Import作為元註解使用時,通過@Import匯入的配置類如果實現了ImportAware介面就可以獲取到匯入該配置類介面的資料配置。有點繞,我們直接上程式碼。

舉個例子:@EnableAsync註解。

@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
//AsyncConfigurationSelector原始碼
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {

    private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
            "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
    @Override
    @Nullable
    public String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                return new String[] {ProxyAsyncConfiguration.class.getName()};
            case ASPECTJ:
                return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
            default:
                return null;
        }
    }
}

預設情況下使用AdviceModePROXY,匯入了ProxyAsyncConfiguration類。

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {

    @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
        Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
        AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
        Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
        if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
            bpp.setAsyncAnnotationType(customAsyncAnnotation);
        }
        if (this.executor != null) {
            bpp.setExecutor(this.executor);
        }
        if (this.exceptionHandler != null) {
            bpp.setExceptionHandler(this.exceptionHandler);
        }
        bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
        bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
        return bpp;
    }
}

ProxyAsyncConfigurationasyncAdvisor方法中需要獲取到@EnableAsync上的一些設定值,例如:this.enableAsync.getBoolean("proxyTargetClass"),this.enableAsync.<Integer>getNumber("order")

this.enableAsync是其父類AbstractAsyncConfiguration的屬性。AbstractAsyncConfiguration實現了ImportAware介面,從而就可以獲取到@EnableAsync上的資訊了。

// AbstractAsyncConfiguration#setImportMetadata 原始碼
public void setImportMetadata(AnnotationMetadata importMetadata) {
    this.enableAsync = AnnotationAttributes.fromMap(
            importMetadata.getAnnotationAttributes(EnableAsync.class.getName(), false));
    if (this.enableAsync == null) {
        throw new IllegalArgumentException(
                "@EnableAsync is not present on importing class " + importMetadata.getClassName());
    }
}

可能這個例子有點複雜的,還有一個稍微簡單一點的例子:EnableRedisHttpSession。關於這個,讀者可以自己去看一下原始碼debug學習一下。


歡迎關注公眾號,大家一起學習成長。