Spring Boot中Profile的恰当运用探索

1个月前发布 gsjqwyl
13 0 0

Spring Boot中Profile的恰当运用探索

所谓Profile,乃是一组用于配置不同环境的相关程序组件的集合。

在实际开发过程中,常常需要在不同环境之间进行切换,比如开发时使用的是开发场的部署环境;测试时,采用的是测试场的部署环境;上线时,运用的是产品场的部署环境。传统的做法是手动更改配置信息,但实际项目中配置信息较多,这样操作起来既麻烦又容易出错,而Profile就是为解决此问题而出现的。

每个Profile都包含一组配置文件以及程序组件等,对应着一个部署环境。只要为每个环境提供相应的Profile,当需要切换不同的部署环境时,只需切换到对应的Profile即可。

配置和切换Profile

声明程序组件与配置文件的Profile有如下几种方式:
1. 使用@Profile注解对@Component、@Configuration、@ConfigurationProperties等注解所修饰的类进行修饰,如此便限制了这些类仅对特定的Profile有效。
2. 通过配置文件的名称来限制Profile。例如,application-dev.properties(或者application-dev.yml)文件仅适用于dev Profile。application-test.properties(或者application-test.yml)文件仅适用于test Profile。
3. 在配置文件中运用特定语法来限制某些属性对特定的Profile有效。这类特殊的配置文件被称为“多Profile配置文件”。

关于@ConfigurationProperties类的必要说明:
1. 若@ConfigurationProperties类是通过@EnableConfigurationProperties注解来启用的,那么需要在@EnableConfigurationProperties注解所在的@Configuration类上使用@Profile注解。
2. 若@ConfigurationProperties类是通过@ConfigurationPropertiesScan注解(扫描方式)来启用的,那么需要在@ConfigurationProperties类自身上使用@Profile注解。

代码示例:\resources\application-default.yml

spring:
  datasource:
    # 指定连接deft数据库
    url: jdbc:mysql://localhost:3306/deft?serverTimezone=UTC
    username: root
    password: 32147

代码示例:\resources\application-dev.yml

spring:
  datasource:
    # 指定连接dev数据库
    url: jdbc:mysql://localhost:3306/dev?serverTimezone=UTC
    username: root
    password: 32147

代码示例:\resources\application-test.yml

spring:
  datasource:
    # 指定连接test数据库
    url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC
    username: root
    password: 32147

代码示例:\resources\application-prod.yml

spring:
  datasource:
    # 指定连接prod数据库
    url: jdbc:mysql://localhost:3306/prod?serverTimezone=UTC
    username: root
    password: 32147

控制器类:

@RestController
// 指定当前控制器的Profile为"default"
@Profile("default")
public class DefaultController
{
   private final DataSource dataSource;
   @Autowired
   public DefaultController(DataSource dataSource)
   {
      this.dataSource = dataSource;
   }
   @GetMapping
   public Map<String, String> hello() throws SQLException
   {
      return Map.of("class", "默认场的控制器","数据库",
            dataSource.getConnection().getCatalog());
   }
}

在运行应用时,可通过spring.profiles.active属性来指定激活哪个Profile,主要有以下几种方式:
1. 通过application.properties(或者application.yml)进行指定。
2. 使用操作系统的SPRING_PROFILES_ACTIVE环境变量来指定。
3. 使用系统属性来指定。
4. 使用命令行参数来指定。

以上4种方式按照加载顺序排列,先加载的属性优先级较低,因此通过命令行参数指定的属性会覆盖前面几种方式指定的属性。

如果程序没有指定spring.profiles.active属性,那么会默认使用default profile。

如果程序中定义了通用的配置文件application.properties(或者application.yml),SpringBoot也会加载它,但特定的profile配置文件优先级更高,会覆盖通用配置文件中的同名属性。

添加活动Profile

除了能够改变激活的Profile之外,SpringBoot还允许添加额外的活动Profile,新增的活动Profile不会完全替换原有的Profile,而是对原有的Profile进行追加:当追加的Profile的配置属性与原有的冲突时,追加的配置属性会覆盖原有的属性,否则依旧使用原有的Profile设定。

添加新Profile的方式:
1. 使用spring.profiles.include属性。
2. 调用SpringApplication的setAdditionalProfiles()方法来添加新的活动Profile。

代码示例:\resources\application-addition.yml

spring:
  datasource:
    # 指定连接addition数据库
    url: jdbc:mysql://localhost:3306/ali2?serverTimezone=UTC

其他配置文件依然使用上一小节的例子,此处不再展示。

配置如下命令行参数:–spring.profiles.active=prod

主程序代码:

@SpringBootApplication
public class App
{
   public static void main(String[] args)
   {
       var app = new SpringApplication(App.class);
       // 添加Profile
       app.setAdditionalProfiles("addition");
      app.run(args);
   }
}

运行结果:

Profile组

在某些情况下,我们按照不同的功能组定义了多个配置文件,但这些配置文件仍然应该属于一个特定的Profile。在这种需求下,可以将它们定义成Profile组。

代码示例:\resources\application-prod.yml

spring:
  datasource:
    # 指定连接prod数据库
    url: jdbc:mysql://localhost:3306/prod?serverTimezone=UTC
    username: root
    password: 32147

代码示例:\resources\application-banner.yml

# 定义图片Banner的大小
spring:
  banner:
    image:
      height: 20
      width: 60
      # 设置字符串的色深
      bitdepth: 4

代码示例:\resources\application-server.yml

server:
  port: 9090

\resources\application.yml文件内容:通过group属性来设定组。

spring:
  profiles:
    # 定义Profile组,该组还包括banner和server两个Profile
    group:
      prod:
        - banner
        - server
    # 将prod设为活动Profile
    active: prod

这里同时加载了上面的prod、banner、server三个Profile。

混合复合类型

当SpringBoot从多个配置文件中加载List类型的属性时,后加载的List集合总会完全替换先加载的List集合,比如:先加载的List集合包含2个元素,后加载的List集合包含1个元素,那么这个List属性最终就只有一个元素。

当SpringBoot从多个配置文件中加载Map类型的属性时,后加载的Map的key-value对会被添加到先加载的Map中,比如:先加载的Map集合有2个key-value对,后加载的Map集合有1个key-value对,最终这个Map属性就会包含3个key-value对。

代码示例:属性处理类

@ConfigurationProperties("crazyit")
public class CrazyitProperties
{
   private final List<Book> list = new ArrayList<>();
    // Book类包含了title、description属性
   private final Map<String, Book> map = new HashMap<>();
   public List<Book> getList()
   {
      return this.list;
   }
   public Map<String, Book> getMap()
   {
      return this.map;
   }
}

代码示例:\resources\application-prod.yml

spring:
  datasource:
    # 指定连接prod数据库
    url: jdbc:mysql://localhost:3306/prod?serverTimezone=UTC
    username: root
    password: 32147
crazyit:
  list:
    - title: a
      description: b
    - title: 疯狂Java讲义
      description: 北京大学信息科学学院的Java推荐教材
  map:
    prod:
      title: 产品级
      description: 产品级

代码示例:\resources\application-banner.properties

crazyit.list[0].title = 疯狂Android讲义
crazyit.list[0].description = 最全面的Adnroid编程图书
crazyit.map["banner"].title = Banner级
crazyit.map["banner"].description = Banner级

代码示例:\resources\application-server.yml

crazyit:
  list:
    - title: 疯狂Spring Boot终极讲义
      description: “此书一出再无书”的Spring Boot
  map:
    server:
      title: 服务器级
      description: 服务器级

\resources\application.yml文件内容

spring:
  profiles:
    # 定义Profile组,该组还包括banner和server两个Profile
    group:
      prod:
        - banner
        - server
    # 将prod设为活动Profile
    active: prod

这里同时加载了上面的prod、banner、server三个Profile。加载顺序为prod->banner->server。

控制器类:

@RestController
@Profile("prod")
public class ProdController
{
   private final DataSource dataSource;
   private CrazyitProperties crazyitProperties;
   @Autowired
   public ProdController(DataSource dataSource,
         CrazyitProperties crazyitProperties)
   {
      this.dataSource = dataSource;
      this.crazyitProperties = crazyitProperties;
   }
   @GetMapping
   public Map<String, Object> hello() throws SQLException
   {
      return Map.of("class", "产品场的控制器","数据库",
            dataSource.getConnection().getCatalog(),
            "crazyit", crazyitProperties);
   }
}

运行结果:

根据环境自动更新Profile

SpringBoot允许使用三个减号(—)将一份 .yml分割成逻辑上的多个片段( . properties文件使用 #— 进行分割),每个片段都会被加载成单独的配置。

当分割成多个配置之后,可通过如下属性指定“条件性”生效:
1. spring.config.activate.on-profile:指定此行配置以下的配置仅当指定的profile激活时才有效。该属性也支持取反运算符(!),比如“!dev”表示非dev profile时有效。
2. spring.config.activate.on-cloud-platform:指定此行配置以下的配置仅当处于指定的云平台上时才有效。

比如配置片段

myprop=always-set
#---
spring.config.activate.on-profile=prod
otherprop=prod-value

myprop的属性为always-set,总是生效。当prod Profile处于活动时,otherprop=prod-value才生效。

比如配置片段

myprop: always-set
---
spring:
  config:
    activate:
      on-cloud-platform: kubernetes
otherprop: other-set

myprop的属性为always-set,总是生效。当应用被部署在k8s云平台时,otherprop=other-set才生效。

代码示例:\resources\application.yml文件内容

spring:
  datasource:
    username: root
    password: root
---
spring:
  config:
    activate:
      on-profile: default
  datasource:
    # 指定连接deft数据库
    url: jdbc:mysql://localhost:3306/deft?serverTimezone=UTC
---
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    # 指定连接dev数据库
    url: jdbc:mysql://localhost:3306/dev?serverTimezone=UTC
---
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    # 指定连接prod数据库
    url: jdbc:mysql://localhost:3306/prod?serverTimezone=UTC
---
spring:
  config:
    activate:
      on-profile: test
  datasource:
    # 指定连接test数据库
    url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC

不知道活动的profile,运行结果

配置如下命令行参数:–spring.profiles.active=prod 启动后运行结果

© 版权声明

相关文章

没有相关内容!

暂无评论

none
暂无评论...