环境与 Profile

Spring Boot 通过 Profile 机制实现多环境配置隔离,让同一套代码在开发、测试、生产环境使用不同的配置,无需手动切换。

核心概念

概念说明
Profile环境标识符(如 devtestprod),激活后对应配置生效
EnvironmentSpring 抽象,聚合了所有 PropertySource,可注入使用
PropertySource配置来源,包括配置文件、系统变量、命令行参数等
spring.profiles.active指定当前激活的 Profile

配置文件与 Profile 绑定

多文件方式(推荐)

每个 Profile 对应独立文件,优先级高于主配置:

src/main/resources/
├── application.yml          # 公共配置
├── application-dev.yml      # 开发环境
├── application-test.yml     # 测试环境
└── application-prod.yml     # 生产环境

application.yml 中指定默认激活的 Profile:

spring:
  profiles:
    default: dev

多文档方式(单文件)

同一个 application.yml 内用 --- 分隔文档块:

# 公共配置
server:
  port: 8080
 
---
spring:
  config:
    activate:
      on-profile: dev
datasource:
  url: jdbc:mysql://localhost:3306/dev_db
 
---
spring:
  config:
    activate:
      on-profile: prod
datasource:
  url: jdbc:mysql://prod-host:3306/prod_db

注意:Spring Boot 2.4 起弃用了 spring.profiles 块,改用 spring.config.activate.on-profile

激活 Profile

配置文件

spring:
  profiles:
    active: dev

命令行参数(优先级最高,覆盖配置文件)

java -jar app.jar --spring.profiles.active=prod

环境变量

export SPRING_PROFILES_ACTIVE=prod
java -jar app.jar

JVM 参数

java -Dspring.profiles.active=prod -jar app.jar

测试中激活

@SpringBootTest
@ActiveProfiles("test")
class UserServiceTest {
    // 使用 application-test.yml 配置
}

相关内容参见 测试

@Profile 注解

条件注册 Bean

@Configuration
public class DataSourceConfig {
 
    @Bean
    @Profile("dev")
    public DataSource devDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }
 
    @Bean
    @Profile("prod")
    public DataSource prodDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(env.getProperty("datasource.url"));
        return new HikariDataSource(config);
    }
}

组合用法

@Profile("!prod")          // 非生产环境
@Profile({"dev", "test"})  // dev 或 test
@Profile("dev & local")    // dev 且 local(Spring 5.1+)

@Conditional 的关系:@Profile 本质上是 @Conditional(ProfileCondition.class) 的封装,更细粒度的条件控制参见 条件注解

Profile 组(Spring Boot 2.4+)

将多个 Profile 组合为一个逻辑组,只需激活组名即可:

spring:
  profiles:
    group:
      prod:
        - prod-db
        - prod-mq
        - prod-cache
      dev:
        - dev-db
        - local-mock

激活 prod 时,prod-dbprod-mqprod-cache 全部生效。

Environment 接口

注入 Environment 可在代码中动态读取当前环境信息:

@Service
public class AppInfoService {
 
    @Autowired
    private Environment env;
 
    public void printInfo() {
        String[] profiles = env.getActiveProfiles();
        String dbUrl = env.getProperty("spring.datasource.url");
        boolean isProd = env.acceptsProfiles(Profiles.of("prod"));
    }
}

@PropertySource

从自定义配置文件加载属性(不支持 YAML,只支持 .properties):

@Configuration
@PropertySource("classpath:custom.properties")
@PropertySource(value = "file:/opt/app/secret.properties", ignoreResourceNotFound = true)
public class AppConfig {
}

若要支持 YAML,需自定义 PropertySourceFactory

配置优先级(从高到低)

Spring Boot 的配置来源覆盖从 CI/CD 流水线到本地打包文件的整条链路,优先级从高到低:

优先级来源典型载体
1命令行参数 --key=valuejava -jar、Dockerfile CMD、Jenkinsfile sh
2OS 环境变量Dockerfile ENVdocker run -e、Jenkinsfile environment {}
3JVM 系统属性 -Dkey=valuejava -D 启动参数
4Nacos 远程配置Spring Cloud Alibaba 拉取的 PropertySource
5外置 application-{profile}.ymljar 包外 ./config/./ 目录
6外置 application.ymljar 包外 ./config/./ 目录
7bootstrap-{profile}.ymlclasspath,引导上下文
8bootstrap.ymlclasspath,引导上下文
9classpath application-{profile}.ymljar 内打包的 Profile 配置
10classpath application.ymljar 内打包的通用默认配置

同一位置下,Profile 文件(application-{profile}.yml)始终比基础文件(application.yml)优先级高。

1. 命令行参数(最高优先级)

--key=value 格式,Spring Boot 将其注册为最高优先级的 PropertySource,可覆盖任何其他来源:

java -jar app.jar --spring.profiles.active=prod --server.port=9090

Jenkinsfile 中传入:

stage('Deploy') {
    steps {
        sh 'java -jar app.jar --spring.profiles.active=prod --server.port=8080'
    }
}

Dockerfile 中传入:

# ENTRYPOINT 固定主命令,CMD 作为默认参数,docker run 时可覆盖 CMD 部分
ENTRYPOINT ["java", "-jar", "app.jar"]
CMD ["--spring.profiles.active=prod"]

2. OS 环境变量

Spring Boot 自动将环境变量映射为属性:大写 + 下划线 → 小写 + 点号(SERVER_PORTserver.port)。

Dockerfile 中设置(镜像级,构建时固化):

ENV SPRING_PROFILES_ACTIVE=prod
ENV SERVER_PORT=8080
ENV SPRING_DATASOURCE_URL=jdbc:mysql://prod-db:3306/mydb

docker run 时覆盖(容器级,运行时灵活):

docker run \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e SERVER_PORT=8080 \
  -e SPRING_DATASOURCE_PASSWORD=secret \
  myapp:latest

docker run -e 与 Dockerfile ENV 同属 OS 环境变量,前者在运行时注入,会覆盖镜像内烘焙的 ENV 值。

Jenkinsfile 中设置(流水线级):

pipeline {
    environment {
        SPRING_PROFILES_ACTIVE = 'prod'
        SERVER_PORT = '8080'
    }
    stages {
        stage('Deploy') {
            steps {
                // 敏感信息通过 Credentials 注入,不写死在 Jenkinsfile 中
                withCredentials([string(credentialsId: 'db-password', variable: 'SPRING_DATASOURCE_PASSWORD')]) {
                    sh 'java -jar app.jar'
                }
            }
        }
    }
}

environment {} 块中的变量在 sh 步骤执行时作为 OS 环境变量注入子进程,Spring Boot 启动时可直接读取。

3. JVM 系统属性

-D 参数,优先级低于环境变量,常用于非 Spring 框架的 JVM 级配置:

java -Dspring.profiles.active=prod \
     -Dserver.port=8080 \
     -jar app.jar

4. Nacos 远程配置

Spring Cloud Alibaba 将 Nacos 配置注册为高优先级 PropertySource(高于本地文件,低于命令行和环境变量)。连接信息写在 bootstrap.yml 中,应用启动时自动拉取:

# bootstrap.yml
spring:
  application:
    name: my-service
  cloud:
    nacos:
      config:
        server-addr: ${NACOS_SERVER:localhost:8848}
        namespace: ${NACOS_NAMESPACE:prod}
        file-extension: yaml
        # 扩展配置,列表靠后的优先级更高
        extension-configs:
          - data-id: common.yaml
            group: GLOBAL
            refresh: true
          - data-id: my-service-prod.yaml
            group: DEFAULT_GROUP
            refresh: true

Nacos 内部优先级:主配置(${spring.application.name}.yaml)> 扩展配置(逆序)> 共享配置(shared-configs)。

若需要本地配置覆盖 Nacos(如本地调试),可设置:

spring:
  cloud:
    config:
      override-none: true

5 & 6. 外置配置文件(jar 包外部)

将配置文件放在 jar 包同级目录或其 config/ 子目录,Spring Boot 启动时自动加载,无需修改 jar 包本身,适合容器化部署时通过 volume 挂载差异化配置:

/opt/app/
├── app.jar
├── config/
│   ├── application.yml          # 外置基础配置(优先级 6)
│   └── application-prod.yml     # 外置 Profile 配置(优先级 5)
└── application.yml              # 次级外置配置

Spring Boot 外部配置搜索顺序(后加载的优先级更高):

classpath:/  →  classpath:/config/  →  file:./  →  file:./config/*/  →  file:./config/

Docker 部署时挂载外部配置目录:

VOLUME ["/opt/app/config"]
docker run -v /host/config:/opt/app/config myapp:latest

7 & 8. bootstrap.yml(引导上下文)

bootstrap 上下文在应用上下文之前启动,专门用于加载连接配置中心的基础参数(Nacos 地址、namespace 等),不应放置业务配置:

# bootstrap.yml —— 只放引导基础设施所需的最小配置
spring:
  application:
    name: my-service
  cloud:
    nacos:
      config:
        server-addr: ${NACOS_SERVER:localhost:8848}
        namespace: ${NACOS_NAMESPACE:dev}

bootstrap 上下文的属性优先级低于应用上下文(application.yml 会覆盖 bootstrap.yml 中同名属性),因此 bootstrap.yml 中不要放需要被 Profile 覆盖的业务值。

9 & 10. classpath 内置配置文件(最低优先级)

打包在 jar 内部,作为所有环境的默认兜底值:

src/main/resources/
├── application.yml          # 公共默认值(优先级 10)
├── application-dev.yml      # dev Profile 默认值(优先级 9)
├── application-test.yml
└── application-prod.yml

完整覆盖链路示例

以数据库密码为例,展示从研发到生产的典型覆盖关系:

classpath application.yml         spring.datasource.password=(不写,无默认)
  ↑ 被覆盖
classpath application-dev.yml     spring.datasource.password=dev123
  ↑ 被覆盖
Nacos 远程配置                     spring.datasource.password=test456(test 环境)
  ↑ 被覆盖
OS 环境变量(Jenkinsfile/Docker)   SPRING_DATASOURCE_PASSWORD=prod_secret
  ↑ 被覆盖(紧急场景)
命令行参数                          --spring.datasource.password=override

属性绑定到 Bean 的方式参见 属性绑定,自动配置中 Profile 的应用参见 自动配置

常见场景

区分日志级别

# application-dev.yml
logging:
  level:
    root: DEBUG
    com.example: TRACE
 
# application-prod.yml
logging:
  level:
    root: WARN

关闭 Swagger(生产环境)

@Bean
@Profile("!prod")
public OpenAPI openAPI() {
    return new OpenAPI().info(new Info().title("API 文档"));
}

数据库初始化脚本

# application-dev.yml
spring:
  sql:
    init:
      mode: always  # 每次启动都执行 schema.sql / data.sql
 
# application-prod.yml
spring:
  sql:
    init:
      mode: never