天天看點

你真正了解 Spring @Import 注解嗎?

作者:JU幫
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
	Class<?>[] value();
}           

通過上面的源碼可以看到,@Import 注解定義很簡單,但是其卻有很大的作用,其作用通過注釋分析,可以歸納為以下四種情況:

  1. 導入其他的 @Configuration 配置類
  2. 導入 ImportSelector 接口的實作類
  3. 導入 ImportBeanDefinitionRegistrar 接口的實作類
  4. 導入普通元件類(v4.2 之後)

下面我們對這四種使用場景一一進行分析。

導入其他配置類

在 SpringBoot 項目中,我們知道在沒有顯示設定掃描包路徑的情況下,預設掃描的範圍是主啟動類同包或其子包下的元件。這樣我們在掃描範圍内定義的 @Configuration 配置類,Spring 會自動掃描到,但是對于在掃描範圍之外或第三方提供的配置類,可以通過 @Import 注解将無法掃描到的配置類導入。

通過下面的小示例示範使用 @Import 導入第三方的配置類,示例項目結構如下:

你真正了解 Spring @Import 注解嗎?

第三方相關的類:

  • 需要注入到 IOC 容器的元件:ThirdPartyComponent
public class ThirdPartyComponent {}           
  • 配置類:ThirdPartyConfig
@Configuration
public class ThirdPartyConfig {

    @Bean
    public ThirdPartyComponent thirdPartyComponent() {
        return new ThirdPartyComponent();
    }
}           
第三方配置類主要作用是将 ThirdPartyComponent 添加到 IOC 容器中

示例項目相關的類:

  • 主啟動類:Demo2Application
@SpringBootApplication
public class Demo2Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Demo2Application.class, args);
        ThirdPartyComponent bean = context.getBean(ThirdPartyComponent.class);
        System.out.println(bean);
    }
}           
作用是啟動 SpringBoot 項目,同時使用其預設掃描規則,即無法掃描到第三方的相關類
  • 配置類:MyAppConfig
@Import(ThirdPartyConfig.class)
@Configuration
public class MyAppConfig {}           
作用是将第三方的配置使用 @Import 注解導入,這樣第三方配置類就可以生效,第三方的元件 ThirdPartyComponent 就可以注入到 IOC 容器中了

導入 ImportSelector 接口的實作類

public interface ImportSelector {

	String[] selectImports(AnnotationMetadata importingClassMetadata);

	@Nullable
	default Predicate<String> getExclusionFilter() {
		return null;
	}

}           

這個接口主要我們使用 selectImports 方法,這個方法作用是将傳回值(字元串數組)中的全限定類名對應的類注入到 IOC 容器中。

注意:Spring 會根據傳回值字元串數組中的全限定類名到相應的檔案路徑中查找類檔案,通過代理生成的類,沒有儲存到資源路徑的話,将代理類的全類名傳回,會報找不到資源的異常,如下所示:
public class CustomImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Object p = Proxy.newProxyInstance(CustomImportSelector.class.getClassLoader(), new Class[]{BaseComponent.class},
                (proxy, method, args) -> null);
        return new String[]{p.getClass().getName()};
    }
}           

運作主啟動類:

java.io.FileNotFoundException: class path resource [com/sun/proxy/$Proxy21.class] 
cannot be opened because it does not exist           

導入 ImportBeanDefinitionRegistrar 接口的實作類

public interface ImportBeanDefinitionRegistrar {

	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator) {
		registerBeanDefinitions(importingClassMetadata, registry);
	}

	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	}
}           

對于我們實際使用中,隻需要重寫第二個兩個參數的方法就可以。這個接口可以實作與 ImportSelector 接口實作類相同的功能(注入指定的 Bean),而且該類對 Bean 的注入更加靈活。我們常常用的 Mybatis 架構就是基于該接口實作 Mapper 接口掃描并将代理後的實作類注入到 IOC 容器中的。

該接口第二個方法上有兩個參數:

  • AnnotationMetadata:同 ImportSelector 接口方法參數,用于提供 @Import 所标注類的元資訊,包括所标注類的資訊和其他注解資訊。
  • BeanDefinitionRegistry:BeanDefinition 的注冊器,可以對 BeanDefinition 進行注冊、移除、查詢等操作,相對于 ImportSelector 接口傳回字元串數組注冊 Bean,可以更加靈活。

對于複雜的 Bean 注冊,經常使用該接口,在下一篇文章中,我們将示範一個實際項目中基于該接口的示例。

導入普通元件類

如果将普通類注冊到 IOC 容器中時,可以使用該種方式。雖然可以實作,但是實際開發中應用較少,通常使用在将第三方元件中的普通類注入到 IOC 容器中時的場景。

@Import

@Import 注解在 Spring 中是一個元注解,可以将其标注在其他注解上,當被 @Import 标注的注解标注在其他配置類上時,Spring 會擷取該配置類的所有注解,包括注解上的元注解,這時 @Import 注解就可以被發現了。

這個特性将在在下一篇文章中,我們将示範一個實際項目中基于該接口的示例。