写了这么久的 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 或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的我的博客,记录全部学习和工做中的博文,欢迎你们前去逛逛