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

本文分享自华为云社区《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。

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

相关推荐

鸿蒙案例上新啦 | 出门打车、到家上课:25个模板带你一屏打通日常生活

如何合理拆分页面组件、如何建立单一可信的状态源、如何让用户在每一步操作中都获得明确反馈。鸿蒙开发实践中心以标准化的应用与元服务模板为入口,将清晰的业务架构与可复用的代码结构直观呈现。不妨立即挑选一个感兴趣的方向进入云端环境,从页面布局、组件封装、状态管理到路由组织,亲手跑通你的鸿蒙实战项目。

华为云官方博客 17

LakeAgent:DWS数据仓库的第二大脑,告别 “翻译式运维”,迈入智能自治时代

和普通 SQL 改写工具不同,LakeAgent 自动做多方案代价对比,主动规避负优化陷阱:补充分区键过滤实现剪枝,扫描分区由 37 收敛至 1,执行代价从 758.28 降至 113.74;未来的数据仓库,将不止能算、能存,更能看懂自己、诊断自己、理解自己。长久以来,数仓的智能都依附于人:信号需要人解读,瓶颈需要人研判,经验困在个体里,无法随底座永续生长。CPU 高、CPU 告警、CPU 诊断、CPU 使用率过高、CPU 满。语法、错误码、计费、性能调优、最佳实践、故障处置等全场景内容。

华为云官方博客 495

如何使用RDS Supabase后端BaaS能力:文件存储(File Storage)

• product-images(公开桶,允许公共读取):策略名称"Public read for product-images",允许SELECT,目标角色anon,策略定义bucket_id = 'product-images'。上传/删除需另配INSERT/DELETE策略,详见SQL。• product-specs(私有桶,仅认证用户访问自己的文件夹):建议拆为上传、读取、删除三条策略,策略定义均为 bucket_id = 'product-specs' 且仅限本人文件夹(详见下文 SQL)。

华为云官方博客 429

如何使用RDS Supabase后端BaaS能力:实时订阅与推送(Realtime)

Realtime 把数据库的每次变更同步到客户端:表里增、删、改一行,订阅者无需刷新页面或主动请求即可秒级收到。三大能力各司其职:• Postgres Changes — 监听表的 INSERT/UPDATE/DELETE,变化即通知• Broadcast — 客户端之间直接互发,适合瞬时通知、协同编辑• Presence — 跟踪频道里“谁在线”典型场景有实时数据展示(订单、库存、监控指标即时刷新)、即时通讯(消息收发、在线名单、“对方正在输入”)、协同编辑与实时通知。

华为云官方博客 418

AI Agent如何通过MCP连接到华为云RDS Supabase应用平台

Schema元数据列出所有可访问的表(按schema分组)。Schema元数据列出表的列及元数据(类型/默认值/约束)。Schema元数据列出所有约束(PK/FK/UNIQUE/CHECK/EXCLUDE)。可按schema/表/类型过滤。Schema元数据列出所有外键关系。可按schema/表过滤。Schema元数据列出所有索引及定义和大小。可按schema/表过滤。Schema元数据列出已应用的数据库迁移记录。Schema元数据列出已安装的PostgreSQL扩展。

华为云官方博客 433

华为大数据Flink让每一 CU 算力,跑出开源 5.74 倍的性能

1. 开源Apache Flink 已成为业界流计算事实标准,但在极端重负载场景下存在性能瓶颈。2. 华为大数据Flink通过多项自研深度优化,在执行计划、算子执行、状态存储三个层面构建协同的优化体系。3. Nexmark Benchmark 测试结果:整体性能提升 5.74 倍,极端重负载场景性能领先最高 18.3 倍。4. 100% 兼容 Apache Flink 1.20,业务代码零改造,无缝迁移。

华为云官方博客 350

LakeAgent实战 | 业务分析篇从一句提问到业务分析

本文以“订单金额变化”分析为例,展示LakeAgent如何通过自然语言提问,结合DataArtsStudio的数据资产与专业技能,帮助业务人员快速定位数据、生成SQL、追踪血缘、生成报告。从问题出发,逐步实现数据发现、变化洞察、原因追溯与团队协作,最终形成可复用的分析流程与企业规则,显著提升数据分析效率与一致性。

华为云官方博客 358

华为云RDS Supabase AI应用已开放公测 :30分钟搭建AI应用后端

认证、存储、实时推送、数据库API——4个后端模块,传统开发需要数周。今天,你只需要30分钟。AI创业公司的全栈开发工程师。上周老板说"下周要上线的AI对话应用,后端你来搞"——认证鉴权、数据库接口、文件存储、实时推送,每一个模块都需要数周搭建,而市场不会等你。小张打开华为云RDS Supabase,30分钟搭好认证、数据库API、对象存储,剩下的时间全用来打磨AI交互体验。基于开源Supabase+华为云RDS PostgreSQL全托管能力,为开发者提供。

华为云官方博客 354

如何使用RDS Supabase后端BaaS能力:数据库函数(Database Function)

上一篇介绍了 Database Data API——建表即自动生成增删改查接口,解决单表读写,但聚合统计、跨表事务、字段自动维护等逻辑已超出其能力边界。本篇推进到数据库函数(Database Function):把业务逻辑封装进数据库,前端通过 .rpc() 调用,由触发器自动响应数据变更。上一篇中,小张基于 Data API 完成了产品目录的建表与基础读写。但新需求随之而来:按分类统计产品数与价格区间、上架新品同时写产品表与定价表、产品修改后自动刷新更新时间。

华为云官方博客 436

如何使用RDS Supabase后端BaaS能力:创表即生成API(Database Data API)

认证、存储、实时推送、数据库API——4个后端模块,传统开发要数周。本系列逐个拆解,首篇聚焦数据库API(Database Data API):建表即得 API,无需手写后端。小张,AI创业公司的全栈工程师。老板让他做个产品目录页,展示产品与价格、支持上下架。传统做法要搭后端、写接口、配权限,工期少说1-2周;而在华为云RDS Supabase,建两张表、应用 SDK 直连,几分钟增删改查全跑通。

华为云官方博客 453

从一句话到一个能出去的链接,AI开发者空间做了什么

自行搭建的 Agent、正在使用的开源项目、团队既有的工具,都可以接入,可以通过AI创建或者手动搭建环境;华为云 AI开发者空间——面向开发者的云端一站式开发平台,做的就是把这段路接上:环境免安装,浏览器打开即为完整的 Linux 运行环境,预置的 AI 智能体开箱即用。第一个任务建议选能出结果的:做一个游戏、生成一个页面、创建一台实例,从需求走到可访问的链接,一次就能看清这个环境在做什么。沙箱是一个完整的 Linux 环境,依赖、服务、端口都是真实的,不是模拟。环境释放后,权限随之失效。

华为云官方博客 411

华为云码道配置自定义大模型,四种方式完整实操

案例介绍。

华为云官方博客 604

图数据库:实现穿透式监管的数据基座

穿透式监管标志着国企监管从“管企业”向“管资本”转型过程中的关键一跃。而图数据库正是这一跃的技术支点。从股权穿透到资金链穿透,从供应链穿透到数据穿透,华为GES图数据库正在将“穿透式监管”从理念变为可落地、可操作的实践。无论是天眼查式的全量链路查询,还是某集团财经的税务规则判断,抑或是芯片供应链式的部件风险分析,乃至通过Palantir构建决策知识图谱,其底层逻辑都是一致的——用图的力量,穿透一切形式的“隔离层”,直抵风险本质与责任核心。

华为云官方博客 652

《公共云三十问》正式收官,奔赴企业AI百场景新征程

今年6月底,公共云产业联盟重点推出《公共云三十问》专栏,以每周三问的节奏,连载十周,解读《先进公共云》白皮书,分享国计民生系列案例,旨在凝聚共识、推动落地实践。同时,专栏分析公共云在产业升级、民生福祉、科技创新、全球发展等方面的作用,梳理中国特色公共云发展路径:以普惠云拓展用云广度,以智算云提升用云深度,以自主云夯实用云厚度,以全球云打开用云跨度。这十问从专属云、行业云切入,深入政务、金融、交通、电力、钢铁、化工、油气、制造等国计民生场景,回答公共云“怎么用”。

华为云官方博客 465

开发者小技巧|IAM配置,让码道代码智能体连续30天免登录

案例介绍。

华为云官方博客 640

码道深度对接CodeArts,让需求自动落地为代码

案例介绍。

华为云官方博客 712

【公共云三十问 之三十】拥抱AI,企业如何建设面向智能体的专属云?

企业可分阶段建设覆盖开发、运行、运营及安全的一体化智能体平台——从构建一站式、高低码协同的智能体开发平台,到打造高性能、极致弹性的运行时环境,再到搭建AgentOps驱动的持续运营与运维体系,同时构建端到端、多层次的全链路安全防护体系。在数据湖建设方面,遵循“存量复用、平滑演进、AI导向、分步迭代”原则,分三阶段推进——打造AI多模数据湖核心能力、完成存算分离与统一元数据管理、实现闭环赋能全面支撑智能体规模化落地,在保护既有投资的前提下将传统数据平台转型为AI数据专属供给平台。

华为云官方博客 818

【公共云三十问 之二十九】国计民生之制造:制造云如何重塑数智时代的新质生产力?

平台打通软件交付的完整路径,覆盖需求管理、代码托管、代码检查、编译构建、制品仓库、部署、测试等关键环节,支持大模型能力融入研发全流程,实现从需求到交付的端到端管理。通过构建“研发一朵云”,方案打破地域与组织壁垒,实现跨团队协同开发,有效缩短软件交付周期,助力工业制造企业从“传统研发”走向“敏捷研发”,加速数智化转型进程。当制造云真正成为数据流通的平台、AI创新的底座,公共云的社会价值将从基础设施层向生产场景层持续延伸,为新型工业化建设和制造强国战略注入源源不断的动力,重塑数智时代的中国制造新质生产力。

华为云官方博客 786

告别重复手工迁仓,码道MCP+Skill实现仓库批量迁移

案例介绍。

华为云官方博客 781

【公共云三十问 之二十八】国计民生之油气:智算云如何叩响数智时代的“地心蓝金”?

西南油气田以“向地球深部要资源”为战略方向,联合以华为云Stack为代表的公共云服务商共同打造统一算力底座,建设西南区域数据湖与通用技术平台,构建“勘探开发工程技术、天然气生产过程管控、天然气产运储销”等三个一体化协同应用场景,支撑油气勘探、开发生产、管输储运等全业务链的数智化转型。当智算云真正成为数据流通的平台、AI创新的底座,油气田得以将算力效能、数据价值与智能技术深度融入勘探开发全链条,将“看不见的算力”转化为“摸得着的储量”,以智能化手段持续降低勘探风险、缩短发现周期、提高采收效率,确保生产安全。

华为云官方博客 612

【公共云三十问 之二十七】国计民生之化工:数智云如何重塑传统化工新范式?

在架构方面,方案构建云边协同的统一体系,涵盖AI协同、数据协同与运营协同三大维度——总部集中训练大模型,边缘节点负责推理执行,云边持续迭代,实现“边用边学”的智能闭环,模型平均精度提升20%以上。此外,方案提供一站式企业级工业物联网平台、数据湖与数据治理平台,覆盖工业数据分析“采、存、算、管、用”全流程,打通生产现场与管理系统间的数据壁垒,有效消除数据孤岛。层面,方案充分发挥软硬协同优势,实现计算、存储、网络与云平台的深度协同,训练线性度超95%,为大模型训练推理提供强劲算力支撑。▲化工“数智”新模式。

华为云官方博客 663

【公共云三十问 之二十六】国计民生之钢铁:数智云如何淬炼中国钢铁新力量?

同时,构建云边端协同的统一架构,支撑集团总部与各生产基地两级协同发展——总部集中训练大模型,边缘节点负责推理执行,云边持续迭代,实现训推一体化的资源调度与高效协同,为钢铁行业的数智化转型构建了坚实的算力底座。层面,方案构建覆盖“工业数采→边缘IT/OT融合→统一数据湖→数据治理平台→数据应用”全链路的一站式数据治理与流通平台,打通生产现场与管理系统间的数据壁垒,让数据“采得到、流得动、用得好”,加速数据价值从生产现场向经营决策层流动释放。湘潭钢铁集团的钢铁数智云,是钢铁工业智能云建设的标杆案例。

华为云官方博客 524

LakeWatch——MRS 智能运维Agent

从资源池、队列、作业等多个维度,对集群CPU与内存资源进行精细化分析与对比,直观呈现不同时间段的空闲、满载等使用状态,识别资源浪费,指导治理优化,为扩缩容决策提供数据支撑。对HDFS存储进行多维度深度分析(目录、用户、用户组等),涵盖使用量、增量、文件大小分布及数据热度等指标,帮助快速识别增长较快的目录、长期未访问的文件,统计目录文件数量与小文件占比,评估HDFS总存储量与扩缩容需求,为存储治理与资源规划提供全面数据支撑。Agent主动巡检,识别异常趋势并提前预警,让运维从"事后处理"转向"事前预防"。

华为云官方博客 511

华为云专题文档发布:丰富的AI案例、选型指导和概念解读

发布 “云服务选型”、“云计算概念”专题,提供云服务选型指导,助您选择匹配业务的云服务,解读云计算相关概念,开启云上学习之路。开发者工具和文档专区上线,提供从学习、实践到反馈的全链路支持。帮助中心新增100余篇AI案例。

华为云官方博客 286

一个人,也能用AI做点“星东西”

华为云星耀创想者大赛-2026华为云OPC创意大赛等你来~

华为云官方博客 289

从需求到可执行工作项:MCP+Skill 让智能体规划真正落地

回顾整个体验,以前让智能体帮我拆需求,它是洋洋洒洒给我列了一大篇 Markdown,看着挺像那么回事,但最后还是得我一条条往 CodeArts Req 里搬,搬完再手动挂父子关系、排日期,拆需求五分钟,搬运两小时。现在好了,MCP 给智能体接上了"手",Skill 给它装上了"项目经理的脑子",一句话下去,34 个工作项连带排期、层级关系全给我建好了,甘特图一拉,齐刷刷都在两周内,节省了多少工作量啊,美滋滋。本次实操在码道代码智能体基础上,构建了一套"需求→可执行工作项"的自动化规划方案。

华为云官方博客 559

华为云码道「码力续航计划」更新!

✅上新GLM‑5.3‑Flash,高速响应,代码生成、工程解析、Agent调试高效输出。新增GLM‑5.3‑Flash福利模型,每日解锁1000万免费Tokens。● 本次活动共发放总量价值100万元Tokens,发完即止!把耗算力的编码重活,交给华为云码道代码智能体🚀🚀。● 每日额度准时刷新,当日清零、次日满血重置。✅复杂算法、大项目解析、批量代码生成随便跑。✅不用抠额度、不用算消耗,放开调试大胆迭代。✅无需额外操作,登录即送,直接切换模型使用。● 把算力交给码道,让撸码不再内耗👏🏻。

华为云官方博客 677

【公共云三十问 之二十四】国计民生之交通:交通云如何铺就数智通衢新坦途?

交通行业作为支撑实体经济的关键基础设施,涵盖公路、铁路、机场、港口、地铁等众多子领域,其中,公路网络作为综合交通运输体系的基础骨架,承担着全国超过70%的客货运量,其数字化、智能化转型的需求尤为迫切。方面,提供Agent开发框架、知识库管理、RAG工程、意图识别、静态应用编排及模型评测等能力,支持客户将行业知识、业务流程与AI能力深度融合,快速构建面向收费稽核、路网调度、应急处置等场景的智能应用。开发并实现落地应用,全面覆盖建设、管理、养护、运营、服务、安全、办公七大业务领域。

华为云官方博客 460

【公共云三十问 之二十五】国计民生之电力:电力云如何点亮数智电力新引擎?

方案构建了覆盖数据获取、质量评估到标准化处理的全链路能力。基于公共云服务商智算底座及气象大模型,通过时空图神经网络重新建模产流机制,中国华电建成超50TB多源融合数据集,突破时空建模与机理融合等五大关键技术,短期径流预测精度较传统机理模型提升5个百分点以上,实现15天中期径流预测精度超85%,推理效率达秒级。基于气象大模型的基础架构与预训练能力,客户可直接在大模型之上开展增量训练与微调,充分利用场站所在区域的高精度历史数据(如3公里、1公里分辨率),针对不同场站和区域的地形地貌与气候特征进行模型调优。

华为云官方博客 380

邀请函|第三届“兴智杯”全国人工智能创新应用大赛开幕式,期待您现场莅临

AI 算法工程师、大模型应用开发、智能编码、国产软硬件栈开发者、开源开发者。大赛官方参赛对象就包含企事业单位开发者团队、个人开发者中华人民共...诉求:了解本届赛题方向、对接算力 / 技术资源、认识同行、获取参赛信息。意向参赛团队、往届参赛选手、技术团队负责人、个人独立开发者。

华为云官方博客 478
上一篇: 数仓性能优化:倾斜优化-表达式计算倾斜的hint优化
下一篇: 一文让你彻底掌握ThreadLocal
华为云开发者联盟
华为云开发者联盟 企业官方账号 企业官方账号
博客等级 码龄10年 72万+粉丝 5984原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值