SpringBoot 系列教程自动配置选择生效

简介: 写了这么久的 Spring 系列博文,发现了一个问题,之前所有的文章都是围绕的让一个东西生效;那么有没有反其道而行之的呢?

image.png


写了这么久的 Spring 系列博文,发现了一个问题,之前所有的文章都是围绕的让一个东西生效;那么有没有反其道而行之的呢?


我们知道可以通过@ConditionOnXxx来决定一个配置类是否可以加载,那么假设有这么个应用场景


  • 有一个 Print 的抽象接口,有多个实现,如输出到控制台的 ConsolePrint, 输出到文件的 FilePrint, 输出到 db 的 DbPrint
  • 我们在实际使用的时候,根据用户的选择,使用其中的一个具体实现


针对上面的 case,当然也可以使用@ConditionOnExpression来实现,除此之外推荐一种更优雅的选择注入方式ImportSelector


I. 配置选择



本文使用的 spring boot 版本为 2.1.2.RELEASE


接下来我们使用 ImportSelector 来实现上面提出的 case


1. Print 类


一个接口类,三个实现类


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");
    }
}
复制代码


2. 选择类


自定义一个 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();
        }
    }
}
复制代码


3. PrintSelector 注解


主要用来注入PrintConfigSelector来生效,其中 value 属性,用来具体选择让哪一个配置生效,默认注册ConsolePrint


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(PrintConfigSelector.class)
public @interface PrintSelector {
    Class<?> value() default PrintConfigSelector.ConsoleConfiguration.class;
}
复制代码


4. 测试


//@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 实现类


II. 扩展



虽然上面通过一个实际的 case 实现来演示了ImportSelector的使用姿势,可以用来选择某些配置类生效。但还有一些其他的知识点,有必要指出一下


通过 ImportSelector 选择的配置类中的 bean 加载顺序,在不强制指定依赖的情况下是怎样的呢?


1. demo 设计


在默认的加载条件下,包下面的 bean 加载顺序是根据命名的排序来的,接下来让我们来创建一个用来测试 bean 加载顺序的 case


  • 同一个包下,创建 6 个 bean: Demo0, DemoA, DemoB, DemoC, DemoD, DemoE
  • 其中Demo0DemoE为普通的 bean
  • 其中DemoA, 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 的前面


2. 加载顺序实测


稍微修改一下前面的启动类,加上@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 都会被加载,根据输出结果来判断默认的加载顺序


image.png


从输出结果来看,先加载普通的 bean 对象;然后再加载 Config2 中定义的 bean,最后则是 Config1 中定义的 bean;


接下来调整一下 ImportSelector 返回的数组对象中,两个配置类的顺序,如果最终输出是 Config1 中定义的 bean 先被加载,那么就可以说明返回的顺序指定了这些配置类中 bean 的加载顺序


image.png


输出的结果印证了我们的猜想


最后一个疑问,在默认的 bean 初始化顺序过程中,普通的 bean 对象加载顺序是否是优于我们通过ImportSelector来注册的 bean 呢?


  • 从输出结果好像是这样的,但是这个 case 并不充分,没法完全验证这个观点,想要确切的搞清楚这一点,还是得通过源码分析(虽然实际上是这样的)


注意


上面的分析只是考虑默认的 bean 初始化顺序,我们依然是可以通过构造方法引入的方式或者@DependOn注解来强制指定 bean 的初始化顺序的


小结


最后小结一下 ImportSelector 的用法


  • 实现接口,返回 String 数组,数组成员为配置类的全路径
  • 在配置类中定义 bean
  • 返回数组中配置类的顺序,指定了配置类中 bean 的默认加载顺序
  • 通过@Import直接来使ImportSelector接口生效


此外还有一个类似的接口DeferredImportSelector,区别在于实现DeferredImportSelector的类优先级会低与直接实现ImportSelector的类,而且可以通过@Order决定优先级;优先级越高的越先被调用执行



相关文章
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
7天前
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
24 0
|
1月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
42 4
|
1月前
|
Java 数据库连接 Maven
springBoot:项目建立&配置修改&yaml的使用&resource 文件夹(二)
本文档介绍了如何创建一个基于Maven的项目,并配置阿里云仓库、数据库连接、端口号、自定义启动横幅及多环境配置等。同时,详细说明了如何使用YAML格式进行配置,以及如何处理静态资源和模板文件。文档还涵盖了Spring Boot项目的`application.properties`和`application.yaml`文件的配置方法,包括设置数据库驱动、URL、用户名、密码等关键信息,以及如何通过配置文件管理不同环境下的应用设置。
|
1月前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
36 0
|
11天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
23 2
 SpringBoot入门(7)- 配置热部署devtools工具
|
9天前
|
Java 数据库连接
SpringBoot配置多数据源实战
第四届光学与机器视觉国际学术会议(ICOMV 2025) 2025 4th International Conference on Optics and Machine Vision
37 8
|
7天前
|
Java 数据库连接 数据库
springboot启动配置文件-bootstrap.yml常用基本配置
以上是一些常用的基本配置项,在实际应用中可能会根据需求有所变化。通过合理配置 `bootstrap.yml`文件,可以确保应用程序在启动阶段加载正确的配置,并顺利启动运行。
14 2
|
18天前
|
Java Spring 容器
SpringBoot读取配置文件的6种方式,包括:通过Environment、@PropertySource、@ConfigurationProperties、@Value读取配置信息
SpringBoot读取配置文件的6种方式,包括:通过Environment、@PropertySource、@ConfigurationProperties、@Value读取配置信息
43 3
|
30天前
|
druid Java Maven