Spring Profile 指南

1、概览

本文将带你了解 Spring 中的 Profile(配置文件),这是 Spring 的核心功能之一。可以把 Bean 配置在不同的 Profile,例如:devtestprod。然后,可以在不同的环境中激活指定的 Profile,以便只加载当前环境所需的 Bean。

2、在 Bean 上使用 @Profile

从简单的开始,看看如何使用 @Profile 注解将 Bean 映射到特定的 Profile。

该注解接受一个(或多个) Profile 名称。

考虑一个场景:有一个 Bean,它只能在开发过程中激活,不能在生产过程中部署。

dev Profile 注解该 Bean,它只会在开发环境中被加载到容器。在生产环境中, dev Profile 不会被激活:

@Component
@Profile("dev")
public class DevDatasourceConfig

Profile 名称也可以用 NOT 运算符作为前缀,如 !dev,以将其从 Profile 排除。

在如下示例中,只有当 dev Profile 未激活时,组件才会被激活:

@Component
@Profile("!dev")
public class DevDatasourceConfig

3、在 XML 中声明 Profile

Profile 也可以用 XML 配置。<beans> 标签有一个 profile 属性,该属性包含以逗号分隔的 Profile 值:

<beans profile="dev">
    <bean id="devDatasourceConfig" 
      class="org.baeldung.profiles.DevDatasourceConfig" />
</beans>

4、设置 Profile

下一步是激活和设置 Profile,以便在容器中注册相应的 Bean。

4.1、WebApplicationInitializer

在 Web 应用程序中,WebApplicationInitializer 可用于以编程式配置 ServletContext

这也是一个非常方便的位置,可以通过编程式设置激活的 Profile:

@Configuration
public class MyWebApplicationInitializer 
  implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
 
        servletContext.setInitParameter(
          "spring.profiles.active", "dev");
    }
}

4.2、ConfigurableEnvironment

还可以直接在环境中设置 Profile:

@Autowired
private ConfigurableEnvironment env;
...
env.setActiveProfiles("someProfile");

4.3、web.xml 中的 Context Parameter

同样,也可以使用 Context Parameter 在 Web 应用的 web.xml 文件中定义激活的 Profile:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/app-config.xml</param-value>
</context-param>
<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>dev</param-value>
</context-param>

4.4、JVM 系统参数

Profile 名称也可以通过 JVM 系统参数传递。这些 Profile 将在应用启动时激活:

-Dspring.profiles.active=dev

4.5、环境变量

在 Unix 环境中,也可以通过环境变量激活 Profile:

export spring_profiles_active=dev

4.6、Maven Profile

Spring Profile 也可以通过在 Maven Profile 中指定 spring.profiles.active 配置属性来激活。

在每个 Maven Profile 中,都可以设置 spring.profiles.active 属性:

<profiles>
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <spring.profiles.active>dev</spring.profiles.active>
        </properties>
    </profile>
    <profile>
        <id>prod</id>
        <properties>
            <spring.profiles.active>prod</spring.profiles.active>
        </properties>
    </profile>
</profiles>

其值将用于替换 application.properties 中的 @spring.profiles.active@ 占位符:

spring.profiles.active=@spring.profiles.active@

现在,需要在 pom.xml 中启用资源过滤功能:

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
    ...
</build>

并附加一个 -P 参数,以切换要应用的 Maven Profile:

mvn clean package -Pprod

该命令将为 prod Profile 打包应用。在应用运行时,它还会应用 spring.profiles.active 的值 prod

4.7、测试中的 @ActiveProfile

使用 @ActiveProfiles 注解可以很容易地指定激活哪些 Profile,从而使测试变得非常简单:

@ActiveProfiles("dev")

到目前为止,我们已经了解了激活 Profile 的多种方法。

  1. web.xml 中的 Context parameter
  2. WebApplicationInitializer
  3. JVM 系统参数
  4. 环境变量
  5. Maven Profile

思考:但是每种方式的优先级是什么?如果从高到低使用多个 Profile 会发生什么情况?

5、Default Profile

任何未指定 Profile 的 Bean 都属于 default Profile。

当没有其他 Profile 处于活动状态时,Spring 还提供了一种设置默认 Profile 的方法 - 使用 spring.profiles.default 属性。

6、获取激活的 Profile

有时,我们可能需要在运行时获取到激活的 Profile 列表。

有两种方法:使用 Environment 接口或 spring.profiles.active 属性。

6.1、使用 Environment

可以通过注入 Environment 对象来访问激活的 Profile:

public class ProfileManager {
@Autowired
   private Environment environment;

    public void getActiveProfiles() {
        for (String profileName : environment.getActiveProfiles()) {
            System.out.println("Currently active profile - " + profileName);
        }  
    }
}

6.2、使用 spring.profiles.active

或者,也可以通过注入 spring.profiles.active 属性来访问激活的 Profile:

@Value("${spring.profiles.active}")
private String activeProfile;

如上,activeProfile 变量包含当前处于活动状态的 Profile 的名称,如果有多个 Profile,则包含用逗号分隔的名称。

不过,如果没有激活的 Profile,就无法创建 Application Context。由于缺少注入变量的占位符,这会抛出 IllegalArgumentException 异常。

为了避免这种情况,可以定义一个默认值:

@Value("${spring.profiles.active:}")
private String activeProfile;

现在,如果没有 Profile 处于活动状态,activeProfile 会是一个空字符串。

如果想像上一个示例那样以 List 形式访问 Profile,可以通过切割 activeProfile 变量来实现:

public class ProfileManager {
    @Value("${spring.profiles.active:}")
    private String activeProfiles;

    public String getActiveProfiles() {
        for (String profileName : activeProfiles.split(",")) {
            System.out.println("Currently active profile - " + profileName);
        }
    }
}

7、示例: 使用 Profile 分离数据源配置

基础知识讲完了,来看一个真实的例子。

假设我们需要同时维护开发环境和生产环境的数据源配置。

创建一个通用接口 DatasourceConfig,两个数据源实现都需要实现该接口:

public interface DatasourceConfig {
    public void setup();
}

以下是开发环境的配置:

@Component
@Profile("dev")
public class DevDatasourceConfig implements DatasourceConfig {
    @Override
    public void setup() {
        System.out.println("Setting up datasource for DEV environment. ");
    }
}

以及生产环境的配置:

@Component
@Profile("production")
public class ProductionDatasourceConfig implements DatasourceConfig {
    @Override
    public void setup() {
       System.out.println("Setting up datasource for PRODUCTION environment. ");
    }
}

现在,创建一个测试并注入 DatasourceConfig 接口;根据激活的 Profile,Spring 将注入 DevDatasourceConfigProductionDatasourceConfig Bean:

public class SpringProfilesWithMavenPropertiesIntegrationTest {
    @Autowired
    DatasourceConfig datasourceConfig;

    public void setupDatasource() {
        datasourceConfig.setup();
    }
}

dev Profile 处于活动状态时,Spring 会注入 DevDatasourceConfig 对象,当调用 setup() 方法时,输出如下:

Setting up datasource for DEV environment.

8、Spring Boot 中的 Profile

Spring Boot 支持迄今为止概述的所有 Profile 配置,并具有一些额外功能。

8.1、激活或设置 Profile

在第 4 节中介绍的初始化参数 spring.profiles.active 也可以在 Spring Boot 中设置为属性,用于定义当前激活 Profile。这是一个标准属性,Spring Boot 会自动获取它:

spring.profiles.active=dev

但是,从 Spring Boot 2.4 开始,该属性不能与 spring.config.activate.on-profile 结合使用,否则会引发 ConfigDataException(即 InvalidConfigDataPropertyExceptionInactiveConfigDataAccessException)。

要以编程式设置 Profile,还可以使用 SpringApplication 类:

SpringApplication.setAdditionalProfiles("dev");

要在 Spring Boot 中使用 Maven 设置 Profile,可以在 pom.xml 中的 spring-boot-maven-plugin 下指定 Profile 名称:

<plugins>
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
            <profiles>
                <profile>dev</profile>
            </profiles>
        </configuration>
    </plugin>
    ...
</plugins>

并执行 Spring Boot 专用的 Maven goal:

mvn spring-boot:run

8.2、特定于 Profile 的 Properties 文件

不过,Spring Boot 带来的与 Profile 相关的最重要功能是特定于 Profile 的 Properties 文件。这些文件必须以 application-{profile}.properties 格式命名。

Spring Boot 会为所有 Profile 自动加载 application.properties 文件中的属性,而仅为指定 Profile 自动加载特定 Profile .properties 文件中的属性。

例如,可以使用名为 application-dev.propertiesapplication-production.properties 的两个文件,为 dev Profile 和 production Profile 配置不同的数据源:

application-production.properties 文件中,可以设置 MySql 数据源:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db
spring.datasource.username=root
spring.datasource.password=root

然后,可以在 application-dev.properties 文件中为 dev Profile 配置相同的属性,但是数据源使用的是 H2 内存数据库:

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa

这样,就可以轻松地为不同的环境提供不同的配置。

在 Spring Boot 2.4 之前,可以通过特定 Profile 的文档激活 Profile。但现在已不再支持;在以后的版本中,框架会在这种情况下抛出 InvalidConfigDataPropertyExceptionInactiveConfigDataAccessException 异常。

8.3、多文档文件

为了进一步简化为不同环境定义属性的过程,甚至可以将所有属性放在同一个文件中,并使用分隔符来表示 Profile。

从 2.4 版开始,除了之前支持的 YAML 之外,Spring Boot 还扩展了对 properties 文件的多文档文件支持。因此,现在可以在同一个 application.properties 中指定 devproduction 属性:

my.prop=used-always-in-all-profiles
#---
spring.config.activate.on-profile=dev
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db
spring.datasource.username=root
spring.datasource.password=root
#---
spring.config.activate.on-profile=production
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa

Spring Boot 会按从上到下的顺序读取该文件。也就是说,如果在上面的示例中,某个属性(例如 my.prop)在最后出现了多次,那么最后一次出现的值将被视为有效值。

8.4、Profile 组

Spring Boot 2.4 中添加的另一项功能是 “Profile Group”。顾名思义,它允许将类似的 Profile 分组。

再考虑一个使用案例,在生产环境中有多个 Profile。例如,在 production 环境中,可能有一个用于数据库的 proddb Profile,以及一个用于调度器的 prodquartz Profile。

要通过 application.properties 文件一次性启用这些 Profile,可以指定:

spring.profiles.group.production=proddb,prodquartz

因此,激活 production Profile 也会激活 proddbprodquartz

9、总结

本文介绍了 Spring Profile 的特性、应用场景,以及如何在 Spring 和 Spring Boot 中定义、使用 Profile。


Ref:https://www.baeldung.com/spring-profiles