gpt4 book ai didi

掌握Spring条件装配的秘密武器

转载 作者:我是一只小鸟 更新时间:2023-08-01 22:31:46 27 4
gpt4 key购买 nike

本文分享自华为云社区《 Spring高手之路9——掌握Spring条件装配的秘密武器 》,作者:砖业洋__.

在Spring框架中,条件装配是一个强大的功能,可以帮助我们更好地管理和控制Bean的创建过程。本文详细解释了如何使用Spring的@Profile和@Conditional注解实现条件装配,通过具体的示例可以更好地理解这两个注解的用法和适用场景。深入研究这些注解,可以帮助提升Spring应用开发的技能,更好地掌握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  注解来分别配置这些环境的数据库.

                          
                            @Configuration

                          
                          
                            public
                          
                          
                            class
                          
                          
                             DataSourceConfiguration {
    @Value(
                          
                          
                            "
                          
                          
                            ${spring.datasource.username}
                          
                          
                            "
                          
                          
                            )
    
                          
                          
                            private
                          
                          
                             String username;

    @Value(
                          
                          
                            "
                          
                          
                            ${spring.datasource.password}
                          
                          
                            "
                          
                          
                            )
    
                          
                          
                            private
                          
                          
                             String password;

    @Value(
                          
                          
                            "
                          
                          
                            ${spring.datasource.url}
                          
                          
                            "
                          
                          
                            )
    
                          
                          
                            private
                          
                          
                             String url;

    @Bean
    @Profile(
                          
                          
                            "
                          
                          
                            dev
                          
                          
                            "
                          
                          
                            )
    
                          
                          
                            public
                          
                          
                             DataSource devDataSource() {
        
                          
                          
                            return
                          
                          
                             DataSourceBuilder.create()
                .username(username)
                .password(password)
                .url(url 
                          
                          + 
                          
                            "
                          
                          
                            ?useSSL=false&serverTimezone=Asia/Shanghai
                          
                          
                            "
                          
                          
                            )
                .driverClassName(
                          
                          
                            "
                          
                          
                            com.mysql.cj.jdbc.Driver
                          
                          
                            "
                          
                          
                            )
                .build();
    }

    @Bean
    @Profile(
                          
                          
                            "
                          
                          
                            test
                          
                          
                            "
                          
                          
                            )
    
                          
                          
                            public
                          
                          
                             DataSource testDataSource() {
        
                          
                          
                            return
                          
                          
                             DataSourceBuilder.create()
                .username(username)
                .password(password)
                .url(url 
                          
                          + 
                          
                            "
                          
                          
                            ?useSSL=false&serverTimezone=Asia/Shanghai
                          
                          
                            "
                          
                          
                            )
                .driverClassName(
                          
                          
                            "
                          
                          
                            com.mysql.cj.jdbc.Driver
                          
                          
                            "
                          
                          
                            )
                .build();
    }

    @Bean
    @Profile(
                          
                          
                            "
                          
                          
                            prod
                          
                          
                            "
                          
                          
                            )
    
                          
                          
                            public
                          
                          
                             DataSource prodDataSource() {
        
                          
                          
                            return
                          
                          
                             DataSourceBuilder.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 :

                          
                            package com.example.demo.bean;


                          
                          
                            public
                          
                          
                            class
                          
                          
                             LibraryOpeningHours {
    
                          
                          
                            private
                          
                          
                             final String openTime;
    
                          
                          
                            private
                          
                          
                             final String closeTime;

    
                          
                          
                            public
                          
                          
                             LibraryOpeningHours(String openTime, String closeTime) {
        
                          
                          
                            this
                          
                          .openTime =
                          
                             openTime;
        
                          
                          
                            this
                          
                          .closeTime =
                          
                             closeTime;
    }

    @Override
    
                          
                          
                            public
                          
                          
                             String toString() {
        
                          
                          
                            return
                          
                          
                            "
                          
                          
                            LibraryOpeningHours{
                          
                          
                            "
                          
                           +
                
                          
                            "
                          
                          
                            openTime='
                          
                          
                            "
                          
                           + openTime + 
                          
                            '
                          
                          
                            \'
                          
                          
                            '
                          
                           +
                
                          
                            "
                          
                          
                            , closeTime='
                          
                          
                            "
                          
                           + closeTime + 
                          
                            '
                          
                          
                            \'
                          
                          
                            '
                          
                           +
                
                          
                            '
                          
                          
                            }
                          
                          
                            '
                          
                          
                            ;
    }
}
                          
                        

然后,我们需要一个  Library  类来持有这个开放时间:

                          
                            package com.example.demo.bean;


                          
                          
                            public
                          
                          
                            class
                          
                          
                             Library {
    
                          
                          
                            private
                          
                          
                             final LibraryOpeningHours openingHours;

    
                          
                          
                            public
                          
                          
                             Library(LibraryOpeningHours openingHours) {
        
                          
                          
                            this
                          
                          .openingHours =
                          
                             openingHours;
    }

    
                          
                          
                            public
                          
                          
                            void
                          
                          
                             displayOpeningHours() {
        System.
                          
                          
                            out
                          
                          .println(
                          
                            "
                          
                          
                            Library opening hours: 
                          
                          
                            "
                          
                           +
                          
                             openingHours);
    }
}
                          
                        

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

                          
                            package com.example.demo.configuration;

import com.example.demo.bean.Library;
import com.example.demo.bean.LibraryOpeningHours;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile(
                          
                          
                            "
                          
                          
                            weekday
                          
                          
                            "
                          
                          
                            )

                          
                          
                            public
                          
                          
                            class
                          
                          
                             WeekdayLibraryConfiguration {
    @Bean
    
                          
                          
                            public
                          
                          
                             LibraryOpeningHours weekdayOpeningHours() {
        
                          
                          
                            return
                          
                          
                            new
                          
                           LibraryOpeningHours(
                          
                            "
                          
                          
                            8:00 AM
                          
                          
                            "
                          
                          , 
                          
                            "
                          
                          
                            6:00 PM
                          
                          
                            "
                          
                          
                            );
    }

    @Bean
    
                          
                          
                            public
                          
                          
                             Library library(LibraryOpeningHours openingHours) {
        
                          
                          
                            return
                          
                          
                            new
                          
                          
                             Library(openingHours);
    }
}
                          
                        

工作日开放时间是早上 8 点晚上 6 点.

                          
                            package com.example.demo.configuration;

import com.example.demo.bean.Library;
import com.example.demo.bean.LibraryOpeningHours;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile(
                          
                          
                            "
                          
                          
                            weekend
                          
                          
                            "
                          
                          
                            )

                          
                          
                            public
                          
                          
                            class
                          
                          
                             WeekendLibraryConfiguration {
    @Bean
    
                          
                          
                            public
                          
                          
                             LibraryOpeningHours weekendOpeningHours() {
        
                          
                          
                            return
                          
                          
                            new
                          
                           LibraryOpeningHours(
                          
                            "
                          
                          
                            10:00 AM
                          
                          
                            "
                          
                          , 
                          
                            "
                          
                          
                            4:00 PM
                          
                          
                            "
                          
                          
                            );
    }

    @Bean
    
                          
                          
                            public
                          
                          
                             Library library(LibraryOpeningHours openingHours) {
        
                          
                          
                            return
                          
                          
                            new
                          
                          
                             Library(openingHours);
    }
}
                          
                        

周末开放时间是早上 10 点,下午 4 点.

最后我们在主程序中运行,并通过选择不同的  Profile  来改变图书馆的开放时间:

                          
                            package com.example.demo.application;

import com.example.demo.bean.Library;
import com.example.demo.configuration.WeekdayLibraryConfiguration;
import com.example.demo.configuration.WeekendLibraryConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;


                          
                          
                            public
                          
                          
                            class
                          
                          
                             DemoApplication {
    
                          
                          
                            public
                          
                          
                            static
                          
                          
                            void
                          
                          
                             main(String[] args) {
        AnnotationConfigApplicationContext context 
                          
                          = 
                          
                            new
                          
                          
                             AnnotationConfigApplicationContext();

        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 .

运行结果:

image.png

如果我们选择  "weekday" Profile ,图书馆的开放时间就会是工作日的时间;如果我们选择  "weekend" Profile ,图书馆的开放时间就会是周末的时间.

注意: register 方法、 @Component 、 @Bean 、 @Import 都是注册 Bean 到 Spring 容器的方式,它们有不同的适用场景和工作方式:

  • register方法:这个方法用于将一个或多个配置类(即标注了 @Configuration 注解的类)注册到 Spring 的 ApplicationContext 中。这个过程是将配置类的元信息添加到上下文中,但并不会立即实例化 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 .

                          
                            package com.example.demo.application;

import com.example.demo.configuration.WeekdayLibraryConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

                          
                          
                            public
                          
                          
                            class
                          
                          
                             DemoApplication {
    
                          
                          
                            public
                          
                          
                            static
                          
                          
                            void
                          
                          
                             main(String[] args) {
        AnnotationConfigApplicationContext context 
                          
                          = 
                          
                            new
                          
                          
                             AnnotationConfigApplicationContext();
        context.getEnvironment().setActiveProfiles(
                          
                          
                            "
                          
                          
                            weekday
                          
                          
                            "
                          
                          
                            );
        context.register(WeekdayLibraryConfiguration.
                          
                          
                            class
                          
                          
                            );
        context.refresh();
        
                          
                          
                            //
                          
                          
                             下面这行调试使用
                          
                          
        String[] beanDefinitionNames =
                          
                             context.getBeanDefinitionNames();
    }
}
                          
                        

运行结果:

image.png

这里可以看到 WeekdayLibraryConfiguration 已经被创建,而 WeekendLibraryConfiguration 并没有创建.

2.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 节的例子修改一下代码,设置一下系统属性 。

image.png

或者这么配置 。

image.png

Spring Boot 主程序:

                          
                            package com.example.demo.application;

import com.example.demo.configuration.WeekendLibraryConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(
                          
                          
                            "
                          
                          
                            com.example
                          
                          
                            "
                          
                          
                            )

                          
                          
                            public
                          
                          
                            class
                          
                          
                             DemoApplication {
    
                          
                          
                            public
                          
                          
                            static
                          
                          
                            void
                          
                          
                             main(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 并没有创建,可以自行调试验证.

运行结果如下:

image.png

  • 配置环境变量SPRING_PROFILES_ACTIVE

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

在 Unix/Linux 系统中,我们可以在启动应用程序之前,使用 export 命令来设置环境变量。例如:

                          export SPRING_PROFILES_ACTIVE=
                          
                            dev
java 
                          
                          -jar myapp.jar
                        

在 Windows 系统中,我们可以使用 set 命令来设置环境变量:

                          
                            set
                          
                           SPRING_PROFILES_ACTIVE=
                          
                            dev
java 
                          
                          -jar myapp.jar
                        

3.SpringApplicationBuilder.profiles(仅Spring Boot可用)

                          
                            package com.example.demo.application;

import com.example.demo.configuration.WeekendLibraryConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(
                          
                          
                            "
                          
                          
                            com.example
                          
                          
                            "
                          
                          
                            )

                          
                          
                            public
                          
                          
                            class
                          
                          
                             DemoApplication {
    
                          
                          
                            public
                          
                          
                            static
                          
                          
                            void
                          
                          
                             main(String[] args) {
        ConfigurableApplicationContext context 
                          
                          = 
                          
                            new
                          
                           SpringApplicationBuilder(DemoApplication.
                          
                            class
                          
                          
                            )
                .profiles(
                          
                          
                            "
                          
                          
                            weekend
                          
                          
                            "
                          
                          
                            )
                .run(args);

        WeekendLibraryConfiguration bean 
                          
                          = context.getBean(WeekendLibraryConfiguration.
                          
                            class
                          
                          
                            );
        System.
                          
                          
                            out
                          
                          
                            .println(bean.weekendOpeningHours());
    }
}
                          
                        

运行结果:

image.png

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

SpringApplication.setDefaultProperties 方法用于设置默认属性,它可以设置一系列的默认属性,其中就包括 spring.profiles.active 属性。当 spring.profiles.active 属性被设置后, Spring 会将其视为当前的激活 Profile .

主程序:

                          
                            package com.example.demo.application;

import com.example.demo.configuration.WeekdayLibraryConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import javax.annotation.Resource;
import java.util.Properties;

@SpringBootApplication
@ComponentScan(basePackages 
                          
                          = 
                          
                            "
                          
                          
                            com.example.demo
                          
                          
                            "
                          
                          
                            )

                          
                          
                            public
                          
                          
                            class
                          
                          
                             DemoApplication {
    @Resource
    
                          
                          
                            private
                          
                          
                             WeekdayLibraryConfiguration weekdayLibraryConfiguration;

    
                          
                          
                            public
                          
                          
                            static
                          
                          
                            void
                          
                          
                             main(String[] args) {
        SpringApplication application 
                          
                          = 
                          
                            new
                          
                           SpringApplication(DemoApplication.
                          
                            class
                          
                          
                            );
        Properties props 
                          
                          = 
                          
                            new
                          
                          
                             Properties();
        props.put(
                          
                          
                            "
                          
                          
                            spring.profiles.active
                          
                          
                            "
                          
                          , 
                          
                            "
                          
                          
                            weekday
                          
                          
                            "
                          
                          
                            );
        application.setDefaultProperties(props);
        ConfigurableApplicationContext context 
                          
                          =
                          
                             application.run(args);
        
                          
                          
                            //
                          
                          
                             验证当前激活Profile
                          
                          
        String[] activeProfiles =
                          
                             context.getEnvironment().getActiveProfiles();
        
                          
                          
                            for
                          
                          
                             (String profile : activeProfiles) {
            System.
                          
                          
                            out
                          
                          .println(
                          
                            "
                          
                          
                            当前活动Profile:
                          
                          
                            "
                          
                           +
                          
                             profile);
        }
    }
}
                          
                        

运行结果:

image.png

5.配置文件属性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 类:

                          
                            package com.example.demo.bean;


                          
                          
                            public
                          
                          
                            class
                          
                          
                             Librarian {
}
                          
                        

创建 Library 类 。

                          
                            package com.example.demo.bean;


                          
                          
                            public
                          
                          
                            class
                          
                          
                             Library {
    
                          
                          
                            //
                          
                          
                             Library class
                          
                          
                            private
                          
                          
                             final String libraryName;

    
                          
                          
                            public
                          
                          
                             Library(String libraryName) {
        
                          
                          
                            this
                          
                          .libraryName =
                          
                             libraryName;
    }

    
                          
                          
                            public
                          
                          
                            void
                          
                          
                             showLibraryName() {
        System.
                          
                          
                            out
                          
                          .println(
                          
                            "
                          
                          
                            Library name: 
                          
                          
                            "
                          
                           +
                          
                             libraryName);
    }
}
                          
                        

接下来,我们创建一个条件类来检查 Librarian 的 bean 定义是否存在:

                          
                            package com.example.demo.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;


                          
                          
                            public
                          
                          
                            class
                          
                          
                             LibrarianCondition implements Condition {

    @Override
    
                          
                          
                            public
                          
                          
                             boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        
                          
                          
                            return
                          
                           context.getBeanFactory().containsBeanDefinition(
                          
                            "
                          
                          
                            librarian
                          
                          
                            "
                          
                          
                            );
    }
}
                          
                        

这里定义了一个名为 LibrarianCondition 的条件类,实现了 Condition 接口并重写了 matches 方法。在 matches 方法中,检查了 Spring 应用上下文中是否存在名为" librarian "的 Bean 定义.

然后,我们需要创建一个配置类,该配置类定义了一个条件,只有当 Librarian bean 存在时, Library bean 才会被创建:

                          
                            package com.example.demo.configuration;

import com.example.demo.bean.Librarian;
import com.example.demo.bean.Library;
import com.example.demo.condition.LibrarianCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration

                          
                          
                            public
                          
                          
                            class
                          
                          
                             LibraryConfiguration {

    
                          
                          
                            /*
                          
                          
                            *
     * 通过注释或取消注释librarian方法,来改变Librarian bean的存在状态,从而观察对Library bean创建的影响。
     *
     * @return
     
                          
                          
                            */
                          
                          
                            
    @Bean
    
                          
                          
                            public
                          
                          
                             Librarian librarian() {
        
                          
                          
                            return
                          
                          
                            new
                          
                          
                             Librarian();
    }

    @Bean
    @Conditional(LibrarianCondition.
                          
                          
                            class
                          
                          
                            )
    
                          
                          
                            public
                          
                          
                             Library library() {
        
                          
                          
                            return
                          
                          
                            new
                          
                           Library(
                          
                            "
                          
                          
                            The Great Library
                          
                          
                            "
                          
                          
                            );
    }
}
                          
                        

在上述代码中,在创建 Library Bean 的方法上,添加了 @Conditional(LibrarianCondition.class) 注解,指定了只有当  LibrarianCondition  返回  true  时, Library bean  才会被创建。然后,我们可以通过注释或取消注释  librarian()  方法来改变  Librarian bean  的存在状态,从而观察它对  Library bean  创建的影响.

最后,在主程序中,我们初始化 Spring 上下文并获取 Library 的 bean :

                          
                            package com.example.demo;

import com.example.demo.configuration.LibraryConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;


                          
                          
                            public
                          
                          
                            class
                          
                          
                             DemoApplication {
    
                          
                          
                            public
                          
                          
                            static
                          
                          
                            void
                          
                          
                             main(String[] args) {
        AnnotationConfigApplicationContext context 
                          
                          = 
                          
                            new
                          
                          
                             AnnotationConfigApplicationContext();
        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 存在时,输出为:

image.png

当 Librarian 不存在时,输出为:

image.png

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 。

                          
                            package com.example.demo.configuration;

import com.example.demo.bean.Librarian;
import com.example.demo.bean.Library;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration

                          
                          
                            public
                          
                          
                            class
                          
                          
                             LibraryConfiguration {

    
                          
                          
                            /*
                          
                          
                            *
     * 通过注释或取消注释librarian方法,来改变Librarian bean的存在状态,从而观察对Library bean创建的影响。
     *
     * @return
     
                          
                          
                            */
                          
                          
                            
    @Bean
    
                          
                          
                            public
                          
                          
                             Librarian librarian() {
        
                          
                          
                            return
                          
                          
                            new
                          
                          
                             Librarian();
    }

    @Bean
    @ConditionalOnBean(Librarian.
                          
                          
                            class
                          
                          
                            )
    
                          
                          
                            public
                          
                          
                             Library library() {
        
                          
                          
                            return
                          
                          
                            new
                          
                           Library(
                          
                            "
                          
                          
                            The Great Library
                          
                          
                            "
                          
                          
                            );
    }
}
                          
                        

如下图:

image.png

接着,将主程序改为 Spring Boot 再启动 。

                          
                            package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication

                          
                          
                            public
                          
                          
                            class
                          
                          
                             DemoApplication {
    
                          
                          
                            public
                          
                          
                            static
                          
                          
                            void
                          
                          
                             main(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 存在时,输出为:

image.png

当 Librarian 不存在时,输出为:

image.png

有人可能会疑问了,会不会有这种可能, 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 或者某个功能。以下面的代码为例:

                          
                            @Configuration

                          
                          
                            public
                          
                          
                            class
                          
                          
                             MyConfiguration {

    @Bean
    @ConditionalOnProperty(name 
                          
                          = 
                          
                            "
                          
                          
                            my.feature.enabled
                          
                          
                            "
                          
                          , havingValue = 
                          
                            "
                          
                          
                            true
                          
                          
                            "
                          
                          , matchIfMissing = 
                          
                            true
                          
                          
                            )
    
                          
                          
                            public
                          
                          
                             MyFeature myFeature() {
        
                          
                          
                            return
                          
                          
                            new
                          
                          
                             MyFeature();
    }
}
                          
                        

在这个例子中, 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
                          
                          
                            "
                          
                          
                            )

                          
                          
                            public
                          
                          
                            class
                          
                          
                             MyService {
    
                          
                          
                            //
                          
                          
                             ...
                          
                          
}
                        

在这个例子中,如果 com.example.SomeClass 存在于 Classpath 中,那么 MyService 就会被创建并添加到 Spring 的 ApplicationContext 中。如果这个类不存在,那么 MyService 就不会被创建.

同样地,我们也可以使用  @ConditionalOnMissingClass  注解来在某个特定的类不存在于 Classpath 时创建某个 Bean ,只需将上面的代码中的  @ConditionalOnClass  替换为  @ConditionalOnMissingClass  即可.

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

比如在  Spring Boot  中,很多自动配置类都会使用条件装配。比如  DataSourceAutoConfiguration  类,这个类负责自动配置数据库连接池,它使用  @ConditionalOnClass  注解来判断  Classpath  中是否存在相关的数据库驱动类,只有当存在相关的数据库驱动类时,才会进行自动配置.

再比如,我们可能开发了一个功能强大的日志记录库,它可以将日志记录到数据库,但是如果用户的项目中没有包含  JDBC  驱动,那么我们的库应该退化到只将日志记录到文件。这个时候就可以使用  @ConditionalOnClass  来检查是否存在  JDBC  驱动,如果存在则创建一个将日志记录到数据库的  Bean ,否则创建一个将日志记录到文件的  Bean .

  。

点击关注,第一时间了解华为云新鲜技术~ 。

最后此篇关于掌握Spring条件装配的秘密武器的文章就讲到这里了,如果你想了解更多关于掌握Spring条件装配的秘密武器的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com