5.3 使用 profile 文件进行配置

当应用程序部署到不同的运行时环境时,通常会有一些配置细节不同。例如,数据库连接的细节在开发环境中可能与在 QA 环境中不一样,在生产环境中可能还不一样。在一个环境中唯一配置属性的一种方法是使用环境变量来指定配置属性,而不是在 application.properties 或 application.yml 中定义它们。

例如,在开发期间,可以依赖于自动配置的嵌入式 H2 数据库。但在生产中,可以将数据库配置属性设置为环境变量,如下所示:

  1. % export SPRING_DATASOURCE_URL=jdbc:mysql://localhost/tacocloud
  2. % export SPRING_DATASOURCE_USERNAME=tacouser
  3. % export SPRING_DATASOURCE_PASSWORD=tacopassword

尽管这样做是可行的,但是将一两个以上的配置属性指定为环境变量就会变得有点麻烦。此外,没有跟踪环境变量更改的好方法,也没有在出现错误时轻松回滚更改的好方法。

相反,我更喜欢利用 Spring profile 文件。profile 文件是一种条件配置类型,其中根据运行时激活的 profile 文件应用或忽略不同的 bean、配置类和配置属性。

例如,假设出于开发和调试的目的,希望使用嵌入式 H2 数据库,并且希望将 Taco Cloud 代码的日志级别设置为 DEBUG。但是在生产中,需要使用一个外部 MySQL 数据库,并将日志记录级别设置为 WARN。在开发环境中,很容易不设置任何数据源属性并获得自动配置的 H2 数据库。至于 DEBUG 级别的日志记录,可以在 application.yml 中设置 logging.level.tacos 属性。

  1. logging:
  2. level:
  3. tacos: DEBUG

这正是开发目的所需要的。但是,如果要将此应用程序部署到生产环境中,而不需要对 application.yml 进行进一步更改,仍然可以获得对于 tacos 包的调试日志和嵌入式 H2 数据库。需要的是定义一个具有适合生产的属性的 profile 文件。

5.3.1 定义特定 profile 的属性

定义特定 profile 文件的属性的一种方法是创建另一个仅包含用于生产的属性的 YAML 或属性文件。文件的名称应该遵循这个约定:application-{profile 名称}.yml 或 application-{profile 名称}.properties。然后可以指定适合该配置文件的配置属性。例如,可以创建一个名为 application-prod.yml 的新文件,包含以下属性:

  1. spring:
  2. datasource:
  3. url: jdbc:mysql://localhost/tacocloud
  4. username: tacouser
  5. password: tacopassword
  6. logging:
  7. level:
  8. tacos: WARN

另一种指定特定 profile 文件的属性的方法只适用于 YAML 配置。它涉及在应用程序中将特定 profile 的属性与非 profile 的属性一起放在 application.yml 中,由三个连字符分隔。将生产属性应用于 application.yml 时,整个 application.yml 应该是这样的:

  1. logging:
  2. level:
  3. tacos: DEBUG
  4. ---
  5. spring:
  6. profiles: prod
  7. datasource:
  8. url: jdbc:mysql://localhost/tacocloud
  9. username: tacouser
  10. password: tacopassword
  11. logging:
  12. level:
  13. tacos: WARN

这个 application.yml 文件由一组三重连字符(—-)分成两个部分。第二部分为 spring.profiles 指定一个值,这个值指示了随后应用于 prod 配置文件的属性。另一方面,第一部分没有为 spring.profiles 指定值。因此,它的属性对于所有 profile 文件都是通用的,或者如果指定的 profile 文件没有设置其他属性,它就是默认的。

无论应用程序运行时哪个配置文件处于活动状态,tacos 包的日志级别都将通过默认配置文件中的属性设置为 DEBUG。但是,如果名为 prod 的配置文件是活动的,那么 logging.level.tacos 属性将会被重写为 WARN。同样,如果 prod 配置文件是活动的,那么数据源属性将设置为使用外部 MySQL 数据库。

通过创建使用 application-{profile 名称}.yml 或 application-{profile 名称}.properties 这种模式命名的其他 YAML 或 properties 文件,可以为任意数量的 profile 文件定义属性。或者在 application.yml 中再输入三个破折号通过 spring.profiles 来指定配置文件名称。然后添加需要的所有特定 profile 文件的属性。

5.3.2 激活 profile 文件

设置特定 profile 属性没有什么意思,除非这些 profile 处于活动状态。但是要如何激活一个 profile 文件呢?让一个 profile 文件处于激活状态需要做的只是将 spring.profiles.active 属性的值指定为需要激活的 profile 的名称。例如,可以像下面这样设置 application.yml 中的这个属性:

  1. spring:
  2. profiles:
  3. active:
  4. - prod

但是这可能是设定一个活动 profile 最糟糕的方式了。如果在 application.yml 中设置了激活的 profile,然后那个 profile 文件就变成了默认 profile 文件,那么就没有达到生产环境特定属性与开发环境特定属性分离的目的。相反,我推荐使用环境变量设置激活的 profile。在生产环境,像下面这样设置 SPRING_PROFILES_ACTIVE:

  1. % export SPRING_PROFILES_ACTIVE=prod

这样设置完成后,部署于那台机器的任何应用程序将会使用 prod profile,同时相应的配置属性将优先于默认配置文件中的属性。

如果使用可执行的 JAR 文件来运行应用程序,你可能也可以通过命令行设置激活的 profile 文件,如下所示:

  1. % java -jar taco-cloud.jar --spring.profiles.active=prod

请注意 spring.profiles.active 属性名包含的是复数单词 profiles。这意味着可以指定多个活动 profiles 文件。通常,这是一个逗号分隔的列表,当它设置一个环境变量:

  1. % export SPRING_PROFILES_ACTIVE=prod,audit,ha

但是在 YAML 中,需要像下面这样指定它:

  1. spring:
  2. profiles:
  3. active:
  4. - prod
  5. - audit
  6. - ha

同样值得注意的是,如果将 Spring 应用程序部署到 Cloud Foundry 中,一个名为 cloud 的配置文件会自动激活。如果 Cloud Foundry 是生产环境,那么需要确保在 cloud profile 文件中指定了特定于生产环境的属性。

事实证明,配置文件只对在 Spring 应用程序中有条件地设置配置属性有用。让我们看看如何声明特定活动 profile 文件的 bean。

5.3.3 有条件地使用 profile 文件创建 bean

有时候,为不同的配置文件提供一组惟一的 bean 是很有用的。通常,不管哪个 profile 文件是活动的,Java 配置类中声明的任何 bean 都会被创建。但是,假设只有在某个配置文件处于活动状态时才需要创建一些 bean,在这种情况下,@Profile 注解可以将 bean 指定为只适用于给定的 profile 文件。

例如,在 TacoCloudApplication 中声明了一个 CommandLineRunner bean,用于在应用程序启动时加载嵌入式数据库中的成分数据。这对于开发来说很好,但是在生产应用程序中是不必要的(也是不受欢迎的)。为了防止在每次应用程序在生产部署中启动时加载成分数据,可以使用 @Profile 像下面这样注解 CommandLineRunner bean 方法:

  1. @Bean
  2. @Profile("dev")
  3. public CommandLineRunner dataLoader(IngredientRepository repo,
  4. UserRepository userRepo, PasswordEncoder encoder) {
  5. ...
  6. }

或是假设需要在 dev profile 或是 qa profile 被激活时创建 CommandLineRunner,在这种情况下,可以列出需要的 profile:

  1. @Bean
  2. @Profile({"dev", "qa"})
  3. public CommandLineRunner dataLoader(IngredientRepository repo,
  4. UserRepository userRepo, PasswordEncoder encoder) {
  5. ...
  6. }

这样成分数据只会在 dev 或是 qa profile 文件被激活时才会被加载。这就意味着需要在开发环境运行应用程序时,将 dev profile 激活。如果这个 CommandLineRunner bean 总是被创建,除非 prod 配置文件是活动的,那就更方便了。在这种情况下,你可以像这样应用 @Profile:

  1. @Bean
  2. @Profile("!prod")
  3. public CommandLineRunner dataLoader(IngredientRepository repo,
  4. UserRepository userRepo, PasswordEncoder encoder){
  5. ...
  6. }

在这里,感叹号 !否定了配置文件名称。实际上,它声明如果 prod 配置文件不是活动的,就会创建 CommandLineRunner bean。

也可以在整个 @Configuration 注解的类上使用 @Profile。例如,假设要将 CommandLineRunner bean 提取到一个名为 DevelopmentConfig 的单独配置类中。然后你可以用 @Profile 来注解 DevelopmentConfig:

  1. @Profile({"!prod", "!qa"})
  2. @Configuration
  3. public class DevelopmentConfig {
  4. @Bean
  5. public CommandLineRunner dataLoader(IngredientRepository repo,
  6. UserRepository userRepo, PasswordEncoder encoder){
  7. ...
  8. }
  9. }

在这里,CommandLineRunner bean(以及在 DevelopmentConfig 中定义的任何其他 bean)仅在 prod 和 qa 配置文件都不活动的情况下才会被创建。