寫了這麼久的 Spring 系列博文,發現了一個問題,以前全部的文章都是圍繞的讓一個東西生效;那麼有沒有反其道而行之的呢?git
咱們知道能夠經過@ConditionOnXxx
來決定一個配置類是否能夠加載,那麼假設有這麼個應用場景github
針對上面的 case,固然也可使用@ConditionOnExpression
來實現,除此以外推薦一種更優雅的選擇注入方式ImportSelector
spring
<!-- more -->數組
本文使用的 spring boot 版本爲 2.1.2.RELEASEide
接下來咱們使用 ImportSelector 來實現上面提出的 casespring-boot
一個接口類,三個實現類源碼分析
public interface IPrint { void print(); } public class ConsolePrint implements IPrint { @Override public void print() { System.out.println("控制檯輸出"); } } public class DbPrint implements IPrint { @Override public void print() { System.out.println("db print"); } } public class FilePrint implements IPrint { @Override public void print() { System.out.println("file print"); } }
自定義一個 PrintConfigSelector 繼承 ImportSelector,主要在實現類中,經過咱們自定義的註解來選擇具體加載三個配置類中的哪個學習
public class PrintConfigSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(PrintSelector.class.getName())); Class config = attributes.getClass("value"); return new String[]{config.getName()}; } public static class ConsoleConfiguration { @Bean public ConsolePrint consolePrint() { return new ConsolePrint(); } } public static class FileConfiguration { @Bean public FilePrint filePrint() { return new FilePrint(); } } public static class DbConfiguration { @Bean public DbPrint dbPrint() { return new DbPrint(); } } }
主要用來注入PrintConfigSelector
來生效,其中 value 屬性,用來具體選擇讓哪個配置生效,默認註冊ConsolePrint
測試
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(PrintConfigSelector.class) public @interface PrintSelector { Class<?> value() default PrintConfigSelector.ConsoleConfiguration.class; }
//@PrintSelector(PrintConfigSelector.FileConfiguration .class) //@PrintSelector(PrintConfigSelector.DbConfiguration .class) @PrintSelector @SpringBootApplication public class Application { public Application(IPrint print) { print.print(); } public static void main(String[] args) { SpringApplication.run(Application.class); } }
在實際的測試中,經過修改@PrintSelector
的 value 來切換不一樣的 Print 實現類
雖然上面經過一個實際的 case 實現來演示了ImportSelector
的使用姿式,能夠用來選擇某些配置類生效。但還有一些其餘的知識點,有必要指出一下
經過 ImportSelector 選擇的配置類中的 bean 加載順序,在不強制指定依賴的狀況下是怎樣的呢?
在默認的加載條件下,包下面的 bean 加載順序是根據命名的排序來的,接下來讓咱們來建立一個用來測試 bean 加載順序的 case
Demo0
, DemoA
, DemoB
, DemoC
, DemoD
, DemoE
Demo0
DemoE
爲普通的 beanDemoA
, DemoC
由配置類 1 註冊DemoB
, DemoD
有配置類 2 註冊具體代碼以下
@Component public class Demo0 { private String name = "demo0"; public Demo0() { System.out.println(name); } } public class DemoA { private String name = "demoA"; public DemoA() { System.out.println(name); } } public class DemoB { private String name = "demoB"; public DemoB() { System.out.println(name); } } public class DemoC { private String name = "demoC"; public DemoC() { System.out.println(name); } } public class DemoD { private String name = "demoD"; public DemoD() { System.out.println(name); } } @Component public class DemoE { private String name = "demoE"; public DemoE() { System.out.println(name); } }
對應的配置類
public class ToSelectorAutoConfig1 { @Bean public DemoA demoA() { return new DemoA(); } @Bean public DemoC demoC() { return new DemoC(); } } public class ToSelectorAutoConfig2 { @Bean public DemoB demoB() { return new DemoB(); } @Bean public DemoD demoD() { return new DemoD(); } } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(ConfigSelector.class) public @interface DemoSelector { String value() default "all"; } public class ConfigSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(DemoSelector.class.getName())); String config = attributes.getString("value"); if ("config1".equalsIgnoreCase(config)) { return new String[]{ToSelectorAutoConfig1.class.getName()}; } else if ("config2".equalsIgnoreCase(config)) { return new String[]{ToSelectorAutoConfig2.class.getName()}; } else { return new String[]{ToSelectorAutoConfig2.class.getName(), ToSelectorAutoConfig1.class.getName()}; } } }
注意一下ConfigSelector
,默認的DemoSelector
註解表示所有加載,返回的數組中,包含兩個配置類,其中 Config2 在 Confgi1 的前面
稍微修改一下前面的啓動類,加上@DemoSelector
註解
PrintSelector(PrintConfigSelector.FileConfiguration .class) //@PrintSelector(PrintConfigSelector.DbConfiguration .class) //@PrintSelector @DemoSelector @SpringBootApplication public class Application { public Application(IPrint print) { print.print(); } public static void main(String[] args) { SpringApplication.run(Application.class); } }
上面的 case 中,咱們定義的六個 bean 都會被加載,根據輸出結果來判斷默認的加載順序
從輸出結果來看,先加載普通的 bean 對象;而後再加載 Config2 中定義的 bean,最後則是 Config1 中定義的 bean;
接下來調整一下 ImportSelector 返回的數組對象中,兩個配置類的順序,若是最終輸出是 Config1 中定義的 bean 先被加載,那麼就能夠說明返回的順序指定了這些配置類中 bean 的加載順序
輸出的結果印證了咱們的猜測
最後一個疑問,在默認的 bean 初始化順序過程當中,普通的 bean 對象加載順序是不是優於咱們經過ImportSelector
來註冊的 bean 呢?
注意
上面的分析只是考慮默認的 bean 初始化順序,咱們依然是能夠經過構造方法引入的方式或者@DependOn
註解來強制指定 bean 的初始化順序的
最後小結一下 ImportSelector 的用法
@Import
直接來使ImportSelector
接口生效此外還有一個相似的接口DeferredImportSelector
,區別在於實現DeferredImportSelector
的類優先級會低與直接實現ImportSelector
的類,並且能夠經過@Order
決定優先級;優先級越高的越先被調用執行
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛