Spring Framework 元件註冊 之 @Import

發表於2019-07-02

Spring Framework 元件註冊 之 @Import

寫在前面

向spring中註冊元件或者叫javaBean是使用spring的功能的前提條件。而且spring也提供了很多種方式,讓我們可以將普通的javaBean註冊到spring容器中,比如前一篇文章Spring Framework 元件註冊 之 @Component中寫的利用@Component註解將普通的javaBean註冊到容器中,本文說的@Import註解也是spring Framework提供的將普通javaBean註冊到容器中,以及後續文章會說的@Configuration,FactoryBean等方式。

@Import 註冊普通Bean

使用@Import註冊一個普通Bean,只需要在@Import註解中指定待註冊Bean的class即可

/**
 * 使用Import註解,註冊一個普通的Bean
 */
@Data
public class TestImport {
    private String id = "@Import";
}

在spring啟動引導類中,新增@Import註解

/**
 * spring 容器啟動引導類
 */
@Import(TestImport.class)
public class TestImportBootstrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(TestImportBootstrap.class);
        System.out.println("context id : " + applicationContext.getId());
        String[] beanNames = applicationContext.getBeanNamesForType(TestImport.class);
        System.out.println("Bean Name is : " + Arrays.toString(beanNames));
        TestImport bean = applicationContext.getBean(TestImport.class);
        System.out.println("TestImport bean : " + bean);
        applicationContext.close();
    }
}

spring容器啟動後,控制檯列印的結果:

context id : org.springframework.context.annotation.AnnotationConfigApplicationContext@21b8d17c
Bean Name is : [com.spring.study.ioc.register.TestImport]
TestImport bean : TestImport(id=@Import)

通過簡單使用@Import註解,便可以將一個普通的javaBean註冊到spring容器中。並且我們可以看到,通過@Import註解預設註冊的元件名稱為該javaBean的全類名

@Import 匯入 配置類

使用@Import註解匯入配置類,就會將配置類中的所有元件註冊到spring容器中。在spring中,並不是@Configuration標註的類才是配置類,但是被@Configuration標註的類會被生成代理物件,spring注入時與不使用@Configuration註解有很大區別,後續會單獨說明此處內容,本文不在贅述。

/**
 * spring元件配置類
 */
//@Configuration 使用@Import匯入時,此註解可以不加
public class TestConfiguration {
    @Bean
    public TestImport testImport() {
        return new TestImport();
    }

    @Bean
    public TestImport testImport2() {
        return new TestImport();
    }
}

@Import註解中指定待匯入的配置類

/**
 * spring 容器啟動引導類
 */
@Import(TestConfiguration.class)
public class TestImportBootstrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(TestImportBootstrap.class);
        System.out.println("context id : " + applicationContext.getId());
        String[] beanNames = applicationContext.getBeanNamesForType(TestImport.class);
        System.out.println("Bean Name is : " + Arrays.toString(beanNames));
        TestImport bean = (TestImport) applicationContext.getBean("testImport");
        System.out.println("TestImport bean : " + bean);
        applicationContext.close();
    }
}

spring容器啟動後,配置類中的註解同樣會被註冊到spring容器中:

context id : org.springframework.context.annotation.AnnotationConfigApplicationContext@21b8d17c
Bean Name is : [testImport, testImport2]
TestImport bean : TestImport(id=@Import)

由結果可以看出,此時註冊的元件名稱即為配置類中指定的元件名稱,並且通過配置類,可以一次匯入多個元件。

@Import 通過ImportSelector 註冊

ImportSelector介面中只定義了一個介面selectImports,通過此介面返回需要註冊的JavaBean的全類名陣列,在使用@Import匯入時,會將介面返回的所有類註冊到spring容器中

/**
 * 通過 ImportSelector 介面註冊元件
 */
@Data
public class TestSelector {
    private String id = "@Import:ImportSelector";
}

自定義實現ImportSelector介面

/**
 * 自定義元件選擇器,通過返回需要註冊的bean的全類名,進行快速的在IOC容器中註冊元件
 */
public class CustomImportSelector implements ImportSelector {

    /**
     * @param importingClassMetadata 標註了@Import配置類上面所有的註解資訊
     */
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{TestSelector.class.getName()};
    }
}

@Import註解中指定ImportSelector實現類

/**
 * spring 容器啟動引導類
 */
@Import(CustomImportSelector.class)
public class TestImportBootstrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(TestImportBootstrap.class);
        System.out.println("context id : " + applicationContext.getId());
        TestSelector bean = applicationContext.getBean(TestSelector.class);
        System.out.println("TestSelector bean : " + bean);
        String[] beanNames = applicationContext.getBeanNamesForType(TestSelector.class);
        System.out.println("bean names:" + Arrays.asList(beanNames));
        applicationContext.close();
    }
}

spring容器啟動後,控制檯列印的結果:

context id : org.springframework.context.annotation.AnnotationConfigApplicationContext@238e0d81
TestSelector bean : TestSelector(id=@Import:ImportSelector)
bean names:[com.spring.study.ioc.register.TestSelector]

由結果可以看出,TestSelector被註冊到了spring容器中。與前面的直接註冊相比,並沒有看出ImportSelector介面的突出特性。本文只是簡單的說明ImportSelector介面具有註冊元件的功能,對於spring容器在啟動時,如何執行BeanDefinitionRegistryPostProcessor來呼叫selectImports方法;如何使用ImportSelector介面實現更復雜的註冊功能,將在後續文章中深入理解。

@Import 通過ImportBeanDefinitionRegistrar 註冊

ImportBeanDefinitionRegistrar介面中只定義了一個registerBeanDefinitions方法,在此方法中,可以獲取到BeanDefinitionRegistry物件,利用此物件,即可手動將需要的元件註冊的spring容器中。在使用BeanDefinitionRegistry物件時,還可以指定元件在spring容器中註冊的bean名稱。

/**
 * 通過 ImportBeanDefinitionRegistrar 介面手動註冊元件
 */
@Data
public class TestRegistrar {
    private String id = "@Import:TestRegistrar";
}

自定義實現ImportBeanDefinitionRegistrar介面

/**
 * 手動註冊元件到IOC容器中
 */
public class CustomImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * @param importingClassMetadata 標註了@Import配置類上面所有的註解資訊
     * @param registry               BeanDefinition註冊器,可以通過此registry手動的向容器中註冊指定的元件
     */
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if (!registry.containsBeanDefinition("testRegistrar")) {
            BeanDefinition definition = new RootBeanDefinition(TestRegistrar.class);
            registry.registerBeanDefinition("testRegistrar", definition);
        }
    }
}

@Import註解中指定ImportBeanDefinitionRegistrar實現類

/**
 * spring 容器啟動引導類
 */
@Import(CustomImportBeanDefinitionRegistrar.class)
public class TestImportBootstrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(TestImportBootstrap.class);
        System.out.println("context id : " + applicationContext.getId());
        TestRegistrar bean = applicationContext.getBean(TestRegistrar.class);
        System.out.println("TestRegistrar bean : " + bean);
        String[] beanNames = applicationContext.getBeanNamesForType(TestSelector.class);
        System.out.println("bean names:" + Arrays.asList(beanNames));
        applicationContext.close();
    }
}

spring容器啟動後,控制檯列印的結果:

context id : org.springframework.context.annotation.AnnotationConfigApplicationContext@238e0d81
TestRegistrar bean : TestRegistrar(id=@Import:TestRegistrar)
bean names:[testRegistrar]

由此可以看出,TestRegistrar被註冊到了spring容器中。與ImportSelector介面一樣,在spring容器啟動時,通過BeanDefinitionRegistryPostProcessor來執行介面方法。

@Import同時指定多種介面註冊

上面的例子中分別說明了使用@Import,通過直接匯入Bean class,配置類,ImportSelector介面,ImportBeanDefinitionRegistrar介面來向spring容器中註冊元件。當然在使用@Import註解時,可以同時指定上面的任意幾種方式進行註冊

/**
 * spring 容器啟動引導類
 *
 * @author TangFD
 * @since 2019/6/25.
 */
@Import({
        TestComponent.class,
        TestConfiguration.class,
        CustomImportSelector.class,
        CustomImportBeanDefinitionRegistrar.class
})
public class TestImportBootstrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(TestImportBootstrap.class);
        System.out.println("context id : " + applicationContext.getId());
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        System.out.println("bean names:" + Arrays.asList(beanNames));
        applicationContext.close();
    }
}

spring容器啟動後,控制檯列印的結果:

context id : org.springframework.context.annotation.AnnotationConfigApplicationContext@238e0d81
bean names:[org.springframework.context.annotation.internalConfigurationAnnotationProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor, org.springframework.context.event.internalEventListenerProcessor, org.springframework.context.event.internalEventListenerFactory, testImportBootstrap, com.spring.study.ioc.register.TestComponent, com.spring.study.ioc.register.TestConfiguration, testImport, testImport2, com.spring.study.ioc.register.TestSelector, testRegistrar]

總結

向spring容器中註冊元件的方式有很多,本文主要說明了如何使用@Import註解向spring容器中註冊元件。並且遺留了一個需要深入理解的知識點:在spring容器啟動時,如何通過執行BeanDefinitionRegistryPostProcessor來執行ImportSelectorImportBeanDefinitionRegistrar介面方法進行元件註冊。此處內容,將在後續的spring容器啟動過程中,分析BeanFactoryPostProcessor介面執行過程裡進行補充。

學習永遠都不是一件簡單的事情,可以有迷茫,可以懶惰,但是前進的腳步永遠都不能停止。

不積跬步,無以至千里;不積小流,無以成江海;

相關文章