0


Spring高手之路9——掌握Spring条件装配的秘密武器

文章目录

1. 条件装配

1.1 理解条件装配及其在Spring中的重要角色

  在

Spring

框架中,条件装配(

Conditional Configuration

)是一个非常重要的特性,它允许开发者根据满足的条件,动态地进行

Bean

的注册或是创建。这样就可以根据不同的环境或配置,创建不同的

Bean

实例,这一特性对于创建可配置和模块化的应用是非常有用的。

Spring

提供了一系列的注解来实现条件装配,包括:

  • @Profile:这是 Spring 的注解,这个注解表示只有当特定的Profile被激活时,才创建带有该注解的Bean,我们可以在应用的配置文件中设置激活的Profile
  • @Conditional:这是 Spring 的注解,它接受一个或多个Condition类,这些类需要实现Condition接口,并重写其matches方法。只有当所有Condition类的matches方法都返回true时,带有@Conditional注解的Bean才会被创建。

以下的注解是

Spring Boot

提供的,主要用于自动配置功能:

  • @ConditionalOnProperty:这个注解表示只有当一个或多个给定的属性有特定的值时,才创建带有该注解的Bean
  • @ConditionalOnClass 和 @ConditionalOnMissingClass:这两个注解表示只有当Classpath中有(或没有)特定的类时,才创建带有该注解的Bean
  • @ConditionalOnBean 和 @ConditionalOnMissingBean:这两个注解表示只有当Spring ApplicationContext中有(或没有)特定的Bean时,才创建带有该注解的Bean

通过组合这些注解,开发者可以实现复杂的条件装配逻辑,灵活地控制

Spring

应用的配置和行为。


2. @Profile

  在

Spring

中,

Profile

用于解决在不同环境下对不同配置的需求,它可以按照特定环境的要求来装配应用程序。例如我们可能有一套配置用于开发环境,另一套配置用于生产环境,就可以使用

Profile

,它可以在运行时决定哪个环境是活动的,进而决定注册哪些

bean

2.1 基于 @Profile 的实际应用场景

  举个例子,我们可能需要使用不同的数据库或不同的服务端点。

  这里我们可以以数据库配置为例。在开发环境、测试环境和生产环境中数据库可能是不同的,我们可以通过

@Profile

注解来分别配置这些环境的数据库。

@ConfigurationpublicclassDataSourceConfiguration{@Value("${spring.datasource.username}")privateString username;@Value("${spring.datasource.password}")privateString password;@Value("${spring.datasource.url}")privateString url;@Bean@Profile("dev")publicDataSourcedevDataSource(){returnDataSourceBuilder.create().username(username).password(password).url(url +"?useSSL=false&serverTimezone=Asia/Shanghai").driverClassName("com.mysql.cj.jdbc.Driver").build();}@Bean@Profile("test")publicDataSourcetestDataSource(){returnDataSourceBuilder.create().username(username).password(password).url(url +"?useSSL=false&serverTimezone=Asia/Shanghai").driverClassName("com.mysql.cj.jdbc.Driver").build();}@Bean@Profile("prod")publicDataSourceprodDataSource(){returnDataSourceBuilder.create().username(username).password(password).url(url +"?useSSL=true&serverTimezone=Asia/Shanghai").driverClassName("com.mysql.cj.jdbc.Driver").build();}}

  实际开发中不同的环境有不同的

Apollo

配置,

Apollo

上写有数据库连接配置,生产和测试的代码不需要多个

Bean

,只需要加载不同的

Apollo

配置建立数据库连接即可。

Apollo

是由携程框架部门开源的一款分布式配置中心,它能够集中化管理应用程序的配置信息。

Apollo

的主要目标是使应用程序可以在运行时动态调整其配置,而无需重新部署。

Apollo

Spring

Profile

解决的是同一个问题——如何管理不同环境的配置,但

Apollo

提供了更强大的功能,特别是在大规模和复杂的分布式系统中。另外,

Apollo

还支持配置的版本控制、审计日志、权限管理等功能,为配置管理提供了全面的解决方案。

2.2 理解 @Profile 的工作原理和用途

我们来用图书馆开放时间例子,并且使用

Spring Profiles

来控制开放时间的配置。

全部代码如下:

首先,我们需要一个表示开放时间的类

LibraryOpeningHours

packagecom.example.demo.bean;publicclassLibraryOpeningHours{privatefinalString openTime;privatefinalString closeTime;publicLibraryOpeningHours(String openTime,String closeTime){this.openTime = openTime;this.closeTime = closeTime;}@OverridepublicStringtoString(){return"LibraryOpeningHours{"+"openTime='"+ openTime +'\''+", closeTime='"+ closeTime +'\''+'}';}}

然后,我们需要一个

Library

类来持有这个开放时间:

packagecom.example.demo.bean;publicclassLibrary{privatefinalLibraryOpeningHours openingHours;publicLibrary(LibraryOpeningHours openingHours){this.openingHours = openingHours;}publicvoiddisplayOpeningHours(){System.out.println("Library opening hours: "+ openingHours);}}

接着,我们需要定义两个不同的配置,分别表示工作日和周末的开放时间:

packagecom.example.demo.configuration;importcom.example.demo.bean.Library;importcom.example.demo.bean.LibraryOpeningHours;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Profile;@Configuration@Profile("weekday")publicclassWeekdayLibraryConfiguration{@BeanpublicLibraryOpeningHoursweekdayOpeningHours(){returnnewLibraryOpeningHours("8:00 AM","6:00 PM");}@BeanpublicLibrarylibrary(LibraryOpeningHours openingHours){returnnewLibrary(openingHours);}}

工作日开放时间是早上

8

点晚上

6

点。

packagecom.example.demo.configuration;importcom.example.demo.bean.Library;importcom.example.demo.bean.LibraryOpeningHours;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Profile;@Configuration@Profile("weekend")publicclassWeekendLibraryConfiguration{@BeanpublicLibraryOpeningHoursweekendOpeningHours(){returnnewLibraryOpeningHours("10:00 AM","4:00 PM");}@BeanpublicLibrarylibrary(LibraryOpeningHours openingHours){returnnewLibrary(openingHours);}}

周末开放时间是早上

10

点,下午

4

点。

最后我们在主程序中运行,并通过选择不同的

Profile

来改变图书馆的开放时间:

packagecom.example.demo.application;importcom.example.demo.bean.Library;importcom.example.demo.configuration.WeekdayLibraryConfiguration;importcom.example.demo.configuration.WeekendLibraryConfiguration;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassDemoApplication{publicstaticvoidmain(String[] args){AnnotationConfigApplicationContext context =newAnnotationConfigApplicationContext();

        context.getEnvironment().setActiveProfiles("weekday");
        context.register(WeekdayLibraryConfiguration.class,WeekendLibraryConfiguration.class);
        context.refresh();Library library = context.getBean(Library.class);
        library.displayOpeningHours();}}

**这里有小伙伴可能会疑问了,为什么

setActiveProfiles

之后还要有

register

refresh

方法?**

setActiveProfiles

方法用于指定哪些

Profile

处于活动状态,而仅仅设置活动的

Profile

并不会触发

Spring

容器去实例化相应的

bean

register

方法是将配置类注册到

Spring

的应用上下文中,它并不会立即创建配置类中声明的

bean

,这些

bean

的创建过程是在上下文的

refresh

阶段中进行的。在这个阶段,

Spring

容器会读取所有的配置类,并对配置类中使用了

@Bean

注解的方法进行解析,然后才会创建并初始化这些

bean

  所以,如果在调用

refresh

方法之前就尝试去获取配置类中的某个

bean

,就会找不到,因为那个

bean

可能还没有被创建。只有在上下文被刷新(也就是调用了

refresh

方法)之后,所有的

bean

才会被创建并注册到

Spring

容器中,然后才能通过上下文获取到这些

bean

运行结果:

在这里插入图片描述

  如果我们选择

"weekday" Profile

,图书馆的开放时间就会是工作日的时间;如果我们选择

"weekend" Profile

,图书馆的开放时间就会是周末的时间。

注意:

register

方法、

@Component

@Bean

@Import

都是注册

Bean

Spring

容器的方式,它们有不同的适用场景和工作方式:

  • register方法:这个方法用于将一个或多个配置类(即标注了@Configuration注解的类)注册到SpringApplicationContext中。这个过程是将配置类的元信息添加到上下文中,但并不会立即实例化Bean。实际的Bean实例化过程会在ApplicationContext刷新时(即调用refresh方法时)发生,而且这个过程可能受到如@Profile, @Conditional等条件装配注解的影响。
  • @Component:这是一个通用性的注解,可以用来标记任何类作为Spring组件。如果一个被@Component注解的类在Spring的组件扫描路径下,那么Spring会自动创建这个类的Bean并添加到容器中。
  • @Bean:这个注解通常用在配置类中的方法上。被@Bean注解的方法表示了如何实例化、配置和初始化一个新的Bean对象。Spring IoC容器将会负责在适当的时候(在ApplicationContext刷新阶段)调用这些方法,创建Bean实例。
  • @Import:这个注解用于在一个配置类中导入其他的配置类。通过使用这个注解,我们可以将Bean的定义分散到多个配置类中,以实现更好的模块化和组织。

  在

Spring

框架中,以上的这些方法和注解共同工作,提供了强大的依赖注入和管理能力,支持我们创建复杂的、模块化的应用。

  在

Spring

框架中,

refresh

方法被用来启动

Spring

应用上下文的生命周期,它主导了

ApplicationContext

Bean

的解析、创建和初始化过程。以下是

refresh

方法在实际操作中的主要步骤:

  1. 读取所有已注册的配置类:refresh方法首先会扫描ApplicationContext中所有已经注册的配置类(通常是标注了@Configuration注解的类)。它会寻找这些配置类中所有被@Bean注解标记的方法。
  2. 解析@Bean方法:对于每一个@Bean方法,Spring会根据方法的签名、返回类型、以及可能的其他注解(例如@Scope@Lazy@Profile等)来决定如何创建和配置对应的Bean
  3. Bean的创建和依赖注入:基于解析得到的信息,Spring IoC容器会按需创建Bean的实例。在实例化Bean后,Spring还会处理这个Bean的依赖关系,即它会自动将这个Bean所依赖的其他Bean注入到它的属性或构造函数参数中。
  4. 初始化:如果Bean实现了InitializingBean接口或者定义了@PostConstruct注解的方法,那么在所有依赖注入完成后,Spring会调用这些初始化方法。

  因此,在调用

refresh

方法之后,我们可以确信所有在配置类中定义的

Bean

都已经被正确地解析、创建、并注册到了

Spring

ApplicationContext

中。这包括了

Bean

的实例化、依赖注入,以及可能的初始化过程。

  上面这些步骤不一定顺序执行,实际上,

Spring

IoC

容器在处理这些步骤时,可能会进行一些优化和调整。具体的处理顺序可能会受到以下因素的影响:

  • Bean的依赖关系:如果一个Bean A依赖于另一个Bean B,那么Spring需要先创建和初始化Bean B,然后才能创建Bean A。这可能会导致Bean的创建顺序与它们在配置类中定义的顺序不同。
  • Bean的生命周期和作用域:例如,如果一个Bean是单例的(默认的作用域),那么它通常会在容器启动时就被创建。但如果它是原型的,那么它只有在被需要时才会被创建。
  • 条件注解:像@Profile@Conditional这样的条件注解也可能影响Bean的创建。如果条件未满足,对应的Bean将不会被创建。

  虽然在一般情况下,

Spring

确实会按照"读取配置类——解析

@Bean

方法——创建

Bean

——依赖注入——初始化"这样的步骤来处理

Bean

的生命周期,但具体的处理过程可能会受到上面提到的各种因素的影响。

请记住这个图书馆开放时间例子,后面有不少举例围绕这个例子展开。

2.3 为什么要有@Profile,application不是有各种环境的配置文件吗?

application-dev.yml

application-test.yml

application-prod.yml

这些配置文件可以用于在不同环境下指定不同的配置参数,比如数据库连接字符串、服务地址等等。

  相比于

application

配置文件,

@Profile

注解在

Spring

应用中提供了更高级别的环境差异控制,它可以控制整个

Bean

或者配置类,而不仅仅是配置参数。有了

@Profile

,我们可以让整个

Bean

或配置类只在特定环境下生效,这意味着我们可以根据环境使用完全不同的

Bean

实现或者不同的配置方式。

  举一个例子,考虑一个邮件服务,我们可能在开发环境中使用一个模拟的邮件服务,只是简单地把邮件内容打印出来,而在生产环境中我们可能需要使用一个实际的邮件服务。我们可以创建两个

Bean

,一个用

@Profile("dev")

注解,一个用

@Profile("prod")

注解。这样,我们就可以在不改动其它代码的情况下,根据环境选择使用哪个邮件服务。

  总的来说,

application-{profile}.yml

文件和

@Profile

注解都是

Spring

提供的环境差异管理工具,它们分别用于管理配置参数和

Bean

/配置类,根据应用的具体需求选择使用。

2.4 如何确定Spring中活动的Profile?

Spring

可以通过多种方式来指定当前的活动

Profile

,优先级排序从高到低如下:

  1. ConfigurableEnvironment.setActiveProfiles
  2. JVM的-Dspring.profiles.active参数或环境变量SPRING_PROFILES_ACTIVE(仅Spring Boot可用)
  3. SpringApplicationBuilder.profiles(仅Spring Boot可用)
  4. SpringApplication.setDefaultProperties(仅Spring Boot可用)
  5. 配置文件属性spring.profiles.active

如果上面都有配置,那么优先级高的会覆盖优先级低的配置。

我们分别来看一下,这里都以

2.1

节的例子作为基础,只修改主程序,就不放其他重复代码了:

  1. ConfigurableEnvironment.setActiveProfiles

  这个是

Spring

框架的

API

,所以不仅可以在

Spring Boot

中使用,也可以在原生

Spring

应用中使用,我们可以通过获取到的

ApplicationContext

环境设置活动的

Profile

packagecom.example.demo.application;importcom.example.demo.configuration.WeekdayLibraryConfiguration;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassDemoApplication{publicstaticvoidmain(String[] args){AnnotationConfigApplicationContext context =newAnnotationConfigApplicationContext();
        context.getEnvironment().setActiveProfiles("weekday");
        context.register(WeekdayLibraryConfiguration.class);
        context.refresh();// 下面这行调试使用String[] beanDefinitionNames = context.getBeanDefinitionNames();}}

运行结果:

这里可以看到

WeekdayLibraryConfiguration

已经被创建,而

WeekendLibraryConfiguration

并没有创建。

  1. JVM的-Dspring.profiles.active参数和环境变量SPRING_PROFILES_ACTIVE(仅Spring Boot可用)

这两个都是

Spring Boot

特性,在原生的

Spring

应用中并没有内置的支持,我们这里用

Spring Boot

的主程序演示

  • 配置-Dspring.profiles.active参数

例如,在启动一个

Spring Boot

应用程序时,我们可以使用以下命令:

-Dspring.profiles.active=weekend

在这个例子中,

-Dspring.profiles.active=weekend

就是设置系统属性的部分,说明只有标记为

@Profile("weekend")

Bean

才会被创建和使用。

我们用上面

2.1

节的例子修改一下代码,设置一下系统属性

在这里插入图片描述

或者这么配置

在这里插入图片描述

Spring Boot

主程序:

packagecom.example.demo.application;importcom.example.demo.configuration.WeekendLibraryConfiguration;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.ConfigurableApplicationContext;importorg.springframework.context.annotation.ComponentScan;@SpringBootApplication@ComponentScan("com.example")publicclassDemoApplication{publicstaticvoidmain(String[] args){ConfigurableApplicationContext context =SpringApplication.run(DemoApplication.class, args);WeekendLibraryConfiguration bean = context.getBean(WeekendLibraryConfiguration.class);System.out.println(bean.weekendOpeningHours());}}

这里只有

Profile

weekend

bean

,而

Profile

weekday

bean

并没有创建,可以自行调试验证。

运行结果如下:

在这里插入图片描述

  • 配置环境变量SPRING_PROFILES_ACTIVE

我们可以在操作系统的环境变量中设置

SPRING_PROFILES_ACTIVE

Unix/Linux

系统中,我们可以在启动应用程序之前,使用

export

命令来设置环境变量。例如:

exportSPRING_PROFILES_ACTIVE=dev
java-jar myapp.jar

Windows

系统中,我们可以使用

set

命令来设置环境变量:

setSPRING_PROFILES_ACTIVE=dev
java-jar myapp.jar
  1. SpringApplicationBuilder.profiles(仅Spring Boot可用)
packagecom.example.demo.application;importcom.example.demo.configuration.WeekendLibraryConfiguration;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.boot.builder.SpringApplicationBuilder;importorg.springframework.context.ConfigurableApplicationContext;importorg.springframework.context.annotation.ComponentScan;@SpringBootApplication@ComponentScan("com.example")publicclassDemoApplication{publicstaticvoidmain(String[] args){ConfigurableApplicationContext context =newSpringApplicationBuilder(DemoApplication.class).profiles("weekend").run(args);WeekendLibraryConfiguration bean = context.getBean(WeekendLibraryConfiguration.class);System.out.println(bean.weekendOpeningHours());}}

运行结果:

在这里插入图片描述

  1. SpringApplication.setDefaultProperties(仅Spring Boot可用)
SpringApplication.setDefaultProperties

方法用于设置默认属性,它可以设置一系列的默认属性,其中就包括

spring.profiles.active

属性。当

spring.profiles.active

属性被设置后,

Spring

会将其视为当前的激活

Profile

主程序:

packagecom.example.demo.application;importcom.example.demo.configuration.WeekdayLibraryConfiguration;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.ConfigurableApplicationContext;importorg.springframework.context.annotation.ComponentScan;importjavax.annotation.Resource;importjava.util.Properties;@SpringBootApplication@ComponentScan(basePackages ="com.example.demo")publicclassDemoApplication{@ResourceprivateWeekdayLibraryConfiguration weekdayLibraryConfiguration;publicstaticvoidmain(String[] args){SpringApplication application =newSpringApplication(DemoApplication.class);Properties props =newProperties();
        props.put("spring.profiles.active","weekday");
        application.setDefaultProperties(props);ConfigurableApplicationContext context = application.run(args);// 验证当前激活ProfileString[] activeProfiles = context.getEnvironment().getActiveProfiles();for(String profile : activeProfiles){System.out.println("当前活动Profile:"+ profile);}}}

运行结果:

在这里插入图片描述

  1. 配置文件属性spring.profiles.active配置文件

application.properties

application.yml

文件中,我们可以设置

spring.profiles.active

属性。

例如,在

application.properties

文件中,我们可以添加以下行:

spring.profiles.active=weekday

application.yml

文件中,我们可以添加以下内容:

spring:profiles:active: weekday

3. @Conditional

3.1 @Conditional注解及其用途

@Conditional

Spring 4.0

中引入的一个核心注解,用于将

Bean

的创建与特定条件相关联。这种特性在

Spring Boot

中被大量使用,以便在满足特定条件时创建和装配

Bean

@Conditional

注解接受一个或多个实现了

Condition

接口的类作为参数。

Condition

接口只有一个名为

matches

的方法,该方法需要返回一个布尔值以指示条件是否满足。如果

matches

方法返回

true

,则

Spring

会创建和装配标记为

@Conditional

Bean

;如果返回

false

,则不会创建和装配这个

Bean

@Conditional

注解的应用非常灵活。它可以用于标记直接或间接使用

@Component

注解的类,包括但不限于

@Configuration

类。此外,它还可以用于标记

@Bean

方法,或者作为元注解组成自定义注解。

  如果一个

@Configuration

类被

@Conditional

注解标记,那么与该类关联的所有

@Bean

方法,以及任何

@Import

@ComponentScan

注解,也都会受到相同的条件限制。这就意味着,只有当

@Conditional

的条件满足时,这些方法和注解才会被处理。

  总的来说,

@Conditional

提供了一种强大的机制,可以用于基于特定条件来控制

Bean

的创建和装配。通过使用它,我们可以更灵活地控制

Spring

应用的配置,以便适应各种不同的运行环境和需求。

3.2 使用@Conditional实现条件装配

  假设我们有一个图书馆应用程序,其中有两个类,

Librarian

Library

,我们希望只有当

Librarian

类存在时,

Library

才被创建。**这个时候

@Profile

就无法实现了,因为

@Profile

无法检查其他

bean

是否存在。**

全部代码如下:

首先,我们创建

Librarian

类:

packagecom.example.demo.bean;publicclassLibrarian{}

创建

Library

packagecom.example.demo.bean;publicclassLibrary{// Library classprivatefinalString libraryName;publicLibrary(String libraryName){this.libraryName = libraryName;}publicvoidshowLibraryName(){System.out.println("Library name: "+ libraryName);}}

接下来,我们创建一个条件类来检查

Librarian

bean

定义是否存在:

packagecom.example.demo.condition;importorg.springframework.context.annotation.Condition;importorg.springframework.context.annotation.ConditionContext;importorg.springframework.core.type.AnnotatedTypeMetadata;publicclassLibrarianConditionimplementsCondition{@Overridepublicbooleanmatches(ConditionContext context,AnnotatedTypeMetadata metadata){return context.getBeanFactory().containsBeanDefinition("librarian");}}

  这里定义了一个名为

LibrarianCondition

的条件类,实现了

Condition

接口并重写了

matches

方法。在

matches

方法中,检查了

Spring

应用上下文中是否存在名为"

librarian

"的

Bean

定义。

  然后,我们需要创建一个配置类,该配置类定义了一个条件,只有当

Librarian bean

存在时,

Library bean

才会被创建:

packagecom.example.demo.configuration;importcom.example.demo.bean.Librarian;importcom.example.demo.bean.Library;importcom.example.demo.condition.LibrarianCondition;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Conditional;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassLibraryConfiguration{/**
     * 通过注释或取消注释librarian方法,来改变Librarian bean的存在状态,从而观察对Library bean创建的影响。
     *
     * @return
     */@BeanpublicLibrarianlibrarian(){returnnewLibrarian();}@Bean@Conditional(LibrarianCondition.class)publicLibrarylibrary(){returnnewLibrary("The Great Library");}}

  在上述代码中,在创建

Library Bean

的方法上,添加了

@Conditional(LibrarianCondition.class)

注解,指定了只有当

LibrarianCondition

返回

true

时,

Library bean

才会被创建。然后,我们可以通过注释或取消注释

librarian()

方法来改变

Librarian bean

的存在状态,从而观察它对

Library bean

创建的影响。

  最后,在主程序中,我们初始化

Spring

上下文并获取

Library

bean

packagecom.example.demo;importcom.example.demo.configuration.LibraryConfiguration;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassDemoApplication{publicstaticvoidmain(String[] args){AnnotationConfigApplicationContext context =newAnnotationConfigApplicationContext();
        context.register(LibraryConfiguration.class);
        context.refresh();System.out.println("IOC容器是否有librarian?"+ context.containsBean("librarian"));System.out.println("IOC容器是否有library?"+ context.containsBean("library"));}}

在这个例子中,

Library

bean

只有在

Librarian

bean

也存在的情况下才会被创建。

Librarian

存在时,输出为:

在这里插入图片描述

Librarian

不存在时,输出为:

在这里插入图片描述

3.2 @Conditional在Spring Boot中的应用

Spring Boot

在很多地方使用了

@Conditional

来实现条件化配置,我们分别来看一下。

3.2.1 @ConditionalOnBean 和 @ConditionalOnMissingBean

@ConditionalOnBean

@ConditionalOnMissingBean

Spring Boot

提供的一对条件注解,用于条件化的创建

Spring Beans

,可以检查

Spring

容器中是否存在特定的

bean

。如果存在(或者不存在)这样的

bean

,那么对应的配置就会被启用(或者被忽略)。

具体来说:

  • @ConditionalOnBean:当 Spring 容器中存在指定类型的 Bean 时,当前被标注的 Bean 才会被创建。
  • @ConditionalOnMissingBean:当 Spring 容器中不存在指定类型的 Bean 时,当前被标注的 Bean 才会被创建。

  我们这里就选取

@ConditionalOnBean

注解进行讲解。把刚刚我们的配置类修改一下,我们删掉实现了

Condition

接口的条件判断类

LibrarianCondition

,将

@Conditional

改为

@ConditionalOnBean
packagecom.example.demo.configuration;importcom.example.demo.bean.Librarian;importcom.example.demo.bean.Library;importorg.springframework.boot.autoconfigure.condition.ConditionalOnBean;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassLibraryConfiguration{/**
     * 通过注释或取消注释librarian方法,来改变Librarian bean的存在状态,从而观察对Library bean创建的影响。
     *
     * @return
     */@BeanpublicLibrarianlibrarian(){returnnewLibrarian();}@Bean@ConditionalOnBean(Librarian.class)publicLibrarylibrary(){returnnewLibrary("The Great Library");}}

如下图:

在这里插入图片描述

接着,将主程序改为

Spring Boot

再启动

packagecom.example.demo;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.ConfigurableApplicationContext;@SpringBootApplicationpublicclassDemoApplication{publicstaticvoidmain(String[] args){ConfigurableApplicationContext context =SpringApplication.run(DemoApplication.class);System.out.println("IOC容器是否有librarian?"+ context.containsBean("librarian"));System.out.println("IOC容器是否有library?"+ context.containsBean("library"));}}

运行结果如下:

Librarian

存在时,输出为:

在这里插入图片描述

Librarian

不存在时,输出为:

在这里插入图片描述

**有人可能会疑问了,会不会有这种可能,

Librarian

Library

后面才注册,导致这个条件会认为

Librarian

不存在?**

  答案是并不会。

Spring

在处理

@Configuration

类时,会预先解析所有的

@Bean

方法,收集所有的

Bean

定义信息,然后按照依赖关系对这些

Bean

进行实例化。

**那如果

Librarian

不是写在配置类的,而是被

@Component

注解修饰的,会不会因为顺序问题导致条件判断为不存在?**

  即使

Librarian

类的定义被

@Component

注解修饰并通过组件扫描注册到

Spring

容器,

Spring

仍然可以正确地处理依赖关系,它依赖的是

Bean

的定义,而不是

Bean

的实例化。

  当

Spring

容器启动时,它会先扫描所有的

Bean

定义并收集信息,这包括由

@Configuration

类的

@Bean

方法定义的,还有通过

@Component

@Service

@Repository

等注解和组件扫描机制注册的。

  当执行到

@ConditionalOnBean

或者

@ConditionalOnMissingBean

的条件判断时,

Spring

已经有了全局的视野,知道所有的

Bean

定义。所以,即使某个

Bean

是通过

@Component

注解定义并由组件扫描机制注册的,也不会因为顺序问题导致判断失败。同样的,

@Conditional

条件判断也不会存在这个问题。

  总的来说,

Spring

提供了强大的依赖管理和自动装配功能,可以确保

Bean

的各种条件判断,无论

Bean

是如何定义和注册的。

3.2.2 @ConditionalOnProperty

  这个注解表示只有当一个或多个给定的属性有特定的值时,才创建带有该注解的

Bean

@ConditionalOnProperty

Spring Boot

中的一个注解,用于检查某个配置属性是否存在,或者是否有特定的值,只有满足条件的情况下,被该注解标记的类或方法才会被创建或执行。这个注解可以用来在不同的环境下开启或关闭某些功能,或者调整功能的行为。

  在实际的业务中,我们可能会根据配置的不同,决定是否启用某个

Bean

或者某个功能。以下面的代码为例:

@ConfigurationpublicclassMyConfiguration{@Bean@ConditionalOnProperty(name ="my.feature.enabled", havingValue ="true", matchIfMissing =true)publicMyFeaturemyFeature(){returnnewMyFeature();}}

  在这个例子中,

MyFeature

这个

Bean

只有在配置文件中

my.feature.enabled

属性的值为

true

时才会被创建。如果配置文件中没有

my.feature.enabled

这个属性,那么因为

matchIfMissing

属性的值为

true

MyFeature

这个

Bean

依然会被创建。

  这样,我们可以通过修改配置文件,灵活地开启或关闭某个功能,而不需要修改代码。比如,我们可能有一些在开发环境和生产环境下行为不同的功能,或者一些可以根据需求开启或关闭的可选功能。实际开发中这些功能也可以考虑使用

Apollo

,只需要在对应环境的

Apollo

配置即可获取对应属性值,从而实现不同功能。

3.2.3 @ConditionalOnClass 和 @ConditionalOnMissingClass

  这两个注解可以检查

Classpath

中是否存在或不存在某个特定的类。

  例如,我们可能有一个服务需要在

Classpath

中存在某个特定的类时才能正常工作,我们可以这样配置:

@Service@ConditionalOnClass(name ="com.example.SomeClass")publicclassMyService{// ...}

  在这个例子中,如果

com.example.SomeClass

存在于

Classpath

中,那么

MyService

就会被创建并添加到

Spring

ApplicationContext

中。如果这个类不存在,那么

MyService

就不会被创建。

  同样地,我们也可以使用

@ConditionalOnMissingClass

注解来在某个特定的类不存在于

Classpath

时创建某个

Bean

,只需将上面的代码中的

@ConditionalOnClass

替换为

@ConditionalOnMissingClass

即可。

  这个

2

个注解实际的业务开发中使用的情况比较少,因为它主要用于处理底层框架或者库的一些通用逻辑。但它在框架或库的开发中确实非常有用,让框架或库可以更加灵活地适应不同的使用环境和配置。

  比如在

Spring Boot

中,很多自动配置类都会使用条件装配。比如

DataSourceAutoConfiguration

类,这个类负责自动配置数据库连接池,它使用

@ConditionalOnClass

注解来判断

Classpath

中是否存在相关的数据库驱动类,只有当存在相关的数据库驱动类时,才会进行自动配置。

  再比如,我们可能开发了一个功能强大的日志记录库,它可以将日志记录到数据库,但是如果用户的项目中没有包含

JDBC

驱动,那么我们的库应该退化到只将日志记录到文件。这个时候就可以使用

@ConditionalOnClass

来检查是否存在

JDBC

驱动,如果存在则创建一个将日志记录到数据库的

Bean

,否则创建一个将日志记录到文件的

Bean


欢迎一键三连~

有问题请留言,大家一起探讨学习

----------------------Talk is cheap, show me the code-----------------------


本文转载自: https://blog.csdn.net/qq_34115899/article/details/131450910
版权归原作者 砖业洋__ 所有, 如有侵权,请联系我们删除。

“Spring高手之路9——掌握Spring条件装配的秘密武器”的评论:

还没有评论