一文学习 SpringBoot 的 application.yml 配置,基于 Spring Boot 3.2.x

在一个中型电商项目中,开发团队遇到了这样的困境:张三在本地开发时使用本地的MySQL,李四在测试环境部署时连接测试数据库,王五在生产环境发布时又需要不同的Redis配置。更糟糕的是,每个环境的敏感信息(密码、密钥)都不相同,如何安全地管理这些配置?

开发人员们最初的做法是:为每个环境创建不同的配置文件,手动修改后提交。结果可想而知:

  • 开发人员A不小心将生产数据库密码提交到了Git
  • 开发人员B忘记修改Redis地址,导致测试环境连不上缓存
  • 开发人员C的本地配置污染了代码仓库

这些问题,都源于对Spring Boot配置文件application.yml的理解不够深入

一、 application.yml 基础概念

1.1 YAML vs Properties

Spring Boot支持两种配置文件格式:.properties.yml。YAML(YAML Ain't Markup Language)因其结构清晰、可读性强,已成为企业级项目的首选。

Properties格式

properties 复制代码
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/demo
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

YAML格式

yaml 复制代码
server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

YAML的优势

  1. 层次结构清晰,缩进表示层级
  2. 支持复杂数据结构(列表、Map)
  3. 注释使用#,与Shell脚本一致
  4. 支持多文档块(通过---分隔)

1.2 Spring Boot 3.2.x 配置加载顺序

理解配置加载顺序是排查配置问题的关键:

复制代码
1. 默认属性(通过SpringApplication.setDefaultProperties定义)
2. @Configuration类上的@PropertySource注解
3. 配置文件(application.properties或application.yml)
4. 随机属性(RandomValuePropertySource)
5. 操作系统环境变量
6. Java系统属性(System.getProperties())
7. JNDI属性(java:comp/env)
8. ServletContext初始化参数
9. ServletConfig初始化参数
10. SPRING_APPLICATION_JSON中的属性(内嵌JSON)
11. 命令行参数
12. 测试属性(@SpringBootTest)
13. 测试@PropertySource注解
14. DevTools全局配置(~/.spring-boot-devtools.properties)

注意后加载的配置会覆盖先加载的配置,我们可以通过环境变量、命令行参数等方式覆盖打包在Jar内的配置。

二、 企业级 application.yml 示例

2.1 完整配置文件(application.yml)

yaml 复制代码
# ============================================
# 应用基础配置
# ============================================
server:
  port: 8080
  servlet:
    context-path: /api
    encoding:
      charset: UTF-8
      force: true
  tomcat:
    max-threads: 200
    min-spare-threads: 10
    connection-timeout: 5000ms
  compression:
    enabled: true
    mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
    min-response-size: 1024

spring:
  # ============================================
  # 应用信息
  # ============================================
  application:
    name: spring-boot-demo
    version: 1.0.0
  
  # ============================================
  # 多环境配置
  # ============================================
  profiles:
    active: @activatedProperties@  # Maven Profile动态替换
  
  # ============================================
  # 数据源配置(MySQL 8.0.x + HikariCP)
  # ============================================
  datasource:
    # 使用环境变量,避免敏感信息硬编码
    url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:demo}?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
    username: ${DB_USER:root}
    password: ${DB_PASS:123456}
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      pool-name: HikariCP
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      connection-test-query: SELECT 1
      validation-timeout: 5000
  
  # ============================================
  # Redis配置(Redis 7.x + Lettuce)
  # ============================================
  data:
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASS:}
      database: 0
      timeout: 2000ms
      lettuce:
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 5
          max-wait: 1000ms
        shutdown-timeout: 100ms
  
  # ============================================
  # JPA/Hibernate配置(可选)
  # ============================================
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect
        format_sql: true
        jdbc:
          batch_size: 20
  
  # ============================================
  # 事务管理
  # ============================================
  transaction:
    rollback-on-commit-failure: true
  
  # ============================================
  # 消息队列(RabbitMQ示例)
  # ============================================
  rabbitmq:
    host: ${RABBITMQ_HOST:localhost}
    port: 5672
    username: ${RABBITMQ_USER:guest}
    password: ${RABBITMQ_PASS:guest}
    virtual-host: /
    listener:
      simple:
        prefetch: 10
        concurrency: 5
        max-concurrency: 20
  
  # ============================================
  # 邮件配置
  # ============================================
  mail:
    host: smtp.example.com
    port: 587
    username: ${MAIL_USER}
    password: ${MAIL_PASS}
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
          connectiontimeout: 5000
          timeout: 5000
          writetimeout: 5000
  
  # ============================================
  # 缓存配置
  # ============================================
  cache:
    type: redis
    redis:
      time-to-live: 600000
      cache-null-values: false
      key-prefix: "cache:"
      use-key-prefix: true
  
  # ============================================
  # 文件上传
  # ============================================
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 20MB
  
  # ============================================
  # 任务执行与调度
  # ============================================
  task:
    execution:
      pool:
        core-size: 8
        max-size: 20
        queue-capacity: 50
        keep-alive: 60s
      thread-name-prefix: async-task-
    scheduling:
      pool:
        size: 5
      thread-name-prefix: scheduled-task-

# ============================================
# MyBatis-Plus配置
# ============================================
mybatis-plus:
  # 扫描mapper接口
  mapper-locations: classpath*:/mapper/**/*.xml
  type-aliases-package: com.example.demo.entity
  global-config:
    db-config:
      # 逻辑删除
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0
      # ID生成策略
      id-type: ASSIGN_ID
    banner: false
  configuration:
    # 下划线转驼峰
    map-underscore-to-camel-case: true
    # 日志实现
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 开启二级缓存
    cache-enabled: true
    # 返回结果映射
    auto-mapping-behavior: full
    # 返回null的字段不显示
    call-setters-on-nulls: true

# ============================================
# SpringDoc OpenAPI配置
# ============================================
springdoc:
  api-docs:
    enabled: true
    path: /v3/api-docs
  swagger-ui:
    enabled: true
    path: /swagger-ui.html
    operations-sorter: method
    tags-sorter: alpha
  show-actuator: true
  default-consumes-media-type: application/json
  default-produces-media-type: application/json
  writer-with-default-pretty-printer: true
  packages-to-scan: com.example.demo.controller
  paths-to-match: /api/**

# ============================================
# 应用监控(Actuator)
# ============================================
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
      base-path: /actuator
  endpoint:
    health:
      show-details: always
      probes:
        enabled: true
    prometheus:
      enabled: true
  metrics:
    export:
      prometheus:
        enabled: true
    tags:
      application: ${spring.application.name}
  info:
    env:
      enabled: true
    java:
      enabled: true
    os:
      enabled: true

# ============================================
# 日志配置
# ============================================
logging:
  # 日志级别
  level:
    root: INFO
    com.example.demo: DEBUG
    org.springframework.web: INFO
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
  # 日志文件
  file:
    name: logs/${spring.application.name}.log
  # 日志格式
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}) - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
  # Logback配置
  logback:
    rollingpolicy:
      max-file-size: 10MB
      max-history: 30
      total-size-cap: 1GB
      clean-history-on-start: false

# ============================================
# 自定义配置
# ============================================
app:
  # 应用配置
  config:
    # 版本信息
    version: 1.0.0
    # 文件上传路径
    upload-path: /data/uploads
    # 临时文件路径
    temp-path: /tmp
    
  # 安全配置
  security:
    # JWT配置
    jwt:
      secret: ${JWT_SECRET:your-jwt-secret-key-change-in-production}
      expiration: 86400
      header: Authorization
      token-prefix: "Bearer "
    # 加密密钥
    encryption:
      aes-key: ${AES_KEY:default-aes-key-change-me}
    
  # 业务配置
  business:
    # 分页配置
    page:
      max-size: 100
      default-size: 20
    # 缓存配置
    cache:
      user-info-ttl: 300
      order-info-ttl: 600
    # 重试配置
    retry:
      max-attempts: 3
      backoff-delay: 1000

2.2 多环境配置文件

开发环境配置(application-dev.yml)

yaml 复制代码
# 开发环境配置
spring:
  config:
    activate:
      on-profile: dev
  
  # 开发环境使用H2内存数据库
  h2:
    console:
      enabled: true
      path: /h2-console
  datasource:
    url: jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=TRUE
    driver-class-name: org.h2.Driver
    username: sa
    password: 
  
  # 开发环境显示详细错误
  mvc:
    throw-exception-if-no-handler-found: true
  web:
    resources:
      add-mappings: false
  
  # 开发环境开启SQL日志
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  
logging:
  level:
    root: INFO
    com.example.demo: DEBUG
    org.hibernate.SQL: DEBUG
    org.hibernate.type: TRACE

测试环境配置(application-test.yml)

yaml 复制代码
# 测试环境配置
spring:
  config:
    activate:
      on-profile: test
  
  datasource:
    url: jdbc:mysql://test-db:3306/demo_test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: ${TEST_DB_USER}
    password: ${TEST_DB_PASS}
  
  data:
    redis:
      host: test-redis
      password: ${TEST_REDIS_PASS}
  
logging:
  level:
    root: INFO
    com.example.demo: INFO
    org.springframework: WARN

生产环境配置(application-prod.yml)

yaml 复制代码
# 生产环境配置
spring:
  config:
    activate:
      on-profile: prod
  
  datasource:
    url: jdbc:mysql://prod-db-master:3306/demo_prod?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=true&requireSSL=true
    username: ${PROD_DB_USER}
    password: ${PROD_DB_PASS}
    hikari:
      maximum-pool-size: 50
      minimum-idle: 10
  
  data:
    redis:
      host: ${PROD_REDIS_CLUSTER}
      password: ${PROD_REDIS_PASS}
      lettuce:
        pool:
          max-active: 50
          max-idle: 20
  
  # 生产环境关闭开发工具
  devtools:
    add-properties: false
  
  # 生产环境使用更安全的配置
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  jackson:
    default-property-inclusion: non_null

# 生产环境关闭Swagger UI
springdoc:
  swagger-ui:
    enabled: false
  api-docs:
    enabled: false

# 生产环境只暴露必要的监控端点
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  endpoint:
    health:
      show-details: when_authorized
    shutdown:
      enabled: false

# 生产环境更严格的日志级别
logging:
  level:
    root: WARN
    com.example.demo: INFO
  file:
    name: /var/log/${spring.application.name}/app.log
  logback:
    rollingpolicy:
      max-file-size: 50MB
      max-history: 90
      total-size-cap: 10GB

2.3 配置属性绑定类

AppConfigProperties.java - 自定义配置绑定类:

java 复制代码
package com.example.demo.config;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import java.time.Duration;
import java.util.List;
import java.util.Map;

/**
 * 自定义应用配置属性
 * 通过@ConfigurationProperties绑定application.yml中的app前缀配置
 * 
 * @author 专栏作者
 * @version 1.0
 * @since 2026-05-04
 */
@Data
@Component
@Validated
@ConfigurationProperties(prefix = "app")
public class AppConfigProperties {
    
    /**
     * 应用配置
     */
    private Config config = new Config();
    
    /**
     * 安全配置
     */
    private Security security = new Security();
    
    /**
     * 业务配置
     */
    private Business business = new Business();
    
    @Data
    public static class Config {
        /**
         * 应用版本
         */
        @NotBlank(message = "应用版本不能为空")
        private String version = "1.0.0";
        
        /**
         * 文件上传路径
         */
        @NotBlank(message = "文件上传路径不能为空")
        private String uploadPath = "/data/uploads";
        
        /**
         * 临时文件路径
         */
        @NotBlank(message = "临时文件路径不能为空")
        private String tempPath = "/tmp";
    }
    
    @Data
    public static class Security {
        /**
         * JWT配置
         */
        private Jwt jwt = new Jwt();
        
        /**
         * 加密配置
         */
        private Encryption encryption = new Encryption();
        
        @Data
        public static class Jwt {
            /**
             * JWT密钥
             */
            @NotBlank(message = "JWT密钥不能为空")
            private String secret = "your-jwt-secret-key-change-in-production";
            
            /**
             * 过期时间(秒)
             */
            @Min(value = 60, message = "JWT过期时间不能少于60秒")
            @Max(value = 2592000, message = "JWT过期时间不能超过30天")
            private Long expiration = 86400L;
            
            /**
             * Header名称
             */
            @NotBlank(message = "JWT Header名称不能为空")
            private String header = "Authorization";
            
            /**
             * Token前缀
             */
            @NotBlank(message = "JWT Token前缀不能为空")
            private String tokenPrefix = "Bearer ";
        }
        
        @Data
        public static class Encryption {
            /**
             * AES加密密钥
             */
            @NotBlank(message = "AES加密密钥不能为空")
            private String aesKey = "default-aes-key-change-me";
        }
    }
    
    @Data
    public static class Business {
        /**
         * 分页配置
         */
        private Page page = new Page();
        
        /**
         * 缓存配置
         */
        private Cache cache = new Cache();
        
        /**
         * 重试配置
         */
        private Retry retry = new Retry();
        
        @Data
        public static class Page {
            /**
             * 最大分页大小
             */
            @Min(value = 1, message = "最大分页大小不能小于1")
            @Max(value = 1000, message = "最大分页大小不能大于1000")
            private Integer maxSize = 100;
            
            /**
             * 默认分页大小
             */
            @Min(value = 1, message = "默认分页大小不能小于1")
            @Max(value = 100, message = "默认分页大小不能大于100")
            private Integer defaultSize = 20;
        }
        
        @Data
        public static class Cache {
            /**
             * 用户信息缓存时间(秒)
             */
            @Min(value = 60, message = "用户信息缓存时间不能少于60秒")
            private Long userInfoTtl = 300L;
            
            /**
             * 订单信息缓存时间(秒)
             */
            @Min(value = 60, message = "订单信息缓存时间不能少于60秒")
            private Long orderInfoTtl = 600L;
        }
        
        @Data
        public static class Retry {
            /**
             * 最大重试次数
             */
            @Min(value = 1, message = "最大重试次数不能小于1")
            @Max(value = 10, message = "最大重试次数不能大于10")
            private Integer maxAttempts = 3;
            
            /**
             * 退避延迟(毫秒)
             */
            @Min(value = 0, message = "退避延迟不能小于0")
            private Long backoffDelay = 1000L;
        }
    }
    
    /**
     * 获取完整的配置信息
     * @return 配置信息Map
     */
    public Map<String, Object> getAllConfig() {
        return Map.of(
            "config", config,
            "security", Map.of(
                "jwt", security.getJwt(),
                "encryption", security.getEncryption()
            ),
            "business", Map.of(
                "page", business.getPage(),
                "cache", business.getCache(),
                "retry", business.getRetry()
            )
        );
    }
}

2.4 配置验证测试类

ConfigurationTest.java - 测试配置是否正确加载:

java 复制代码
package com.example.demo;

import com.example.demo.config.AppConfigProperties;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * 配置加载测试
 * 验证application.yml配置是否正确加载
 * 
 * @author 专栏作者
 * @version 1.0
 * @since 2026-05-04
 */
@Slf4j
@SpringBootTest
class ConfigurationTest {
    
    @Autowired
    private Environment environment;
    
    @Autowired
    private AppConfigProperties appConfigProperties;
    
    @Test
    void testBasicConfiguration() {
        // 测试基本配置
        String appName = environment.getProperty("spring.application.name");
        String serverPort = environment.getProperty("server.port");
        
        log.info("应用名称: {}", appName);
        log.info("服务器端口: {}", serverPort);
        
        assertThat(appName).isEqualTo("spring-boot-demo");
        assertThat(serverPort).isEqualTo("8080");
    }
    
    @Test
    void testDatabaseConfiguration() {
        // 测试数据库配置
        String driverClass = environment.getProperty("spring.datasource.driver-class-name");
        Integer maxPoolSize = environment.getProperty("spring.datasource.hikari.maximum-pool-size", Integer.class);
        
        log.info("数据库驱动: {}", driverClass);
        log.info("连接池最大大小: {}", maxPoolSize);
        
        assertThat(driverClass).isEqualTo("com.mysql.cj.jdbc.Driver");
        assertThat(maxPoolSize).isEqualTo(20);
    }
    
    @Test
    void testCustomConfiguration() {
        // 测试自定义配置
        log.info("应用版本: {}", appConfigProperties.getConfig().getVersion());
        log.info("上传路径: {}", appConfigProperties.getConfig().getUploadPath());
        log.info("JWT密钥: {}", appConfigProperties.getSecurity().getJwt().getSecret());
        log.info("最大分页大小: {}", appConfigProperties.getBusiness().getPage().getMaxSize());
        
        assertThat(appConfigProperties.getConfig().getVersion()).isEqualTo("1.0.0");
        assertThat(appConfigProperties.getConfig().getUploadPath()).isEqualTo("/data/uploads");
        assertThat(appConfigProperties.getBusiness().getPage().getMaxSize()).isEqualTo(100);
    }
    
    @Test
    void testProfileConfiguration() {
        // 测试环境配置
        String[] activeProfiles = environment.getActiveProfiles();
        log.info("激活的环境: {}", (Object) activeProfiles);
        
        // 如果没有显式设置,使用默认环境
        if (activeProfiles.length == 0) {
            log.info("使用默认配置");
        } else {
            for (String profile : activeProfiles) {
                log.info("环境 {} 的配置已加载", profile);
            }
        }
    }
    
    @Test
    void testConfigurationHierarchy() {
        // 测试配置层次结构
        log.info("=== 配置层次结构测试 ===");
        
        // 1. 系统属性
        String javaVersion = System.getProperty("java.version");
        log.info("Java版本: {}", javaVersion);
        
        // 2. 环境变量
        String userHome = System.getenv("HOME");
        log.info("用户目录: {}", userHome);
        
        // 3. Spring配置
        String appNameFromEnv = environment.getProperty("spring.application.name");
        log.info("从Environment获取应用名称: {}", appNameFromEnv);
        
        // 验证配置来源
        assertThat(appNameFromEnv).isEqualTo("spring-boot-demo");
        
        // 打印所有配置
        log.info("=== 当前所有配置 ===");
        log.info("数据库URL: {}", environment.getProperty("spring.datasource.url"));
        log.info("Redis主机: {}", environment.getProperty("spring.data.redis.host"));
        log.info("MyBatis日志实现: {}", environment.getProperty("mybatis-plus.configuration.log-impl"));
        log.info("Swagger UI路径: {}", environment.getProperty("springdoc.swagger-ui.path"));
    }
}

运行测试结果

复制代码
2026-05-04 10:30:15.123 INFO  com.example.demo.ConfigurationTest - 应用名称: spring-boot-demo
2026-05-04 10:30:15.124 INFO  com.example.demo.ConfigurationTest - 服务器端口: 8080
2026-05-04 10:30:15.125 INFO  com.example.demo.ConfigurationTest - 数据库驱动: com.mysql.cj.jdbc.Driver
2026-05-04 10:30:15.125 INFO  com.example.demo.ConfigurationTest - 连接池最大大小: 20
2026-05-04 10:30:15.126 INFO  com.example.demo.ConfigurationTest - 应用版本: 1.0.0
2026-05-04 10:30:15.126 INFO  com.example.demo.ConfigurationTest - 上传路径: /data/uploads
2026-05-04 10:30:15.126 INFO  com.example.demo.ConfigurationTest - JWT密钥: your-jwt-secret-key-change-in-production
2026-05-04 10:30:15.127 INFO  com.example.demo.ConfigurationTest - 最大分页大小: 100
2026-05-04 10:30:15.127 INFO  com.example.demo.ConfigurationTest - 激活的环境: []
2026-05-04 10:30:15.127 INFO  com.example.demo.ConfigurationTest - 使用默认配置
2026-05-04 10:30:15.127 INFO  com.example.demo.ConfigurationTest - === 配置层次结构测试 ===
2026-05-04 10:30:15.128 INFO  com.example.demo.ConfigurationTest - Java版本: 17.0.5
2026-05-04 10:30:15.128 INFO  com.example.demo.ConfigurationTest - 用户目录: /Users/username
2026-05-04 10:30:15.128 INFO  com.example.demo.ConfigurationTest - 从Environment获取应用名称: spring-boot-demo
2026-05-04 10:30:15.129 INFO  com.example.demo.ConfigurationTest - === 当前所有配置 ===
2026-05-04 10:30:15.129 INFO  com.example.demo.ConfigurationTest - 数据库URL: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
2026-05-04 10:30:15.129 INFO  com.example.demo.ConfigurationTest - Redis主机: localhost
2026-05-04 10:30:15.130 INFO  com.example.demo.ConfigurationTest - MyBatis日志实现: org.apache.ibatis.logging.stdout.StdOutImpl
2026-05-04 10:30:15.130 INFO  com.example.demo.ConfigurationTest - Swagger UI路径: /swagger-ui.html

三、 企业级最佳实践

3.1 配置管理原则

  1. 配置外部化:永远不要在代码中硬编码配置
  2. 环境隔离:使用Profile严格区分不同环境
  3. 敏感信息保护:密码、密钥等通过环境变量注入
  4. 配置验证 :使用@Validated注解验证配置
  5. 配置监控:通过Actuator端点监控配置状态

3.2 安全配置实践

yaml 复制代码
# 安全配置示例
security:
  # 永远不要硬编码密码
  # ❌ 错误做法
  # password: mysecretpassword
  
  # ✅ 正确做法 - 使用环境变量
  password: ${DB_PASSWORD}
  
  # ✅ 或者使用配置中心
  # spring.cloud.config.uri: http://config-server:8888
  
  # 生产环境强制使用SSL
  datasource:
    url: jdbc:mysql://prod-db:3306/db?useSSL=true&requireSSL=true&verifyServerCertificate=true
  
  # 加密敏感配置
  jasypt:
    encryptor:
      password: ${JASYPT_PASSWORD}
      algorithm: PBEWithMD5AndDES

3.3 性能优化配置

yaml 复制代码
# 性能优化配置
spring:
  datasource:
    hikari:
      # 连接池配置
      maximum-pool-size: ${DB_POOL_SIZE:20}
      minimum-idle: ${DB_MIN_IDLE:5}
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      
  data:
    redis:
      lettuce:
        pool:
          # Redis连接池
          max-active: 20
          max-idle: 10
          min-idle: 5
          max-wait: 1000ms
          
  # 线程池配置
  task:
    execution:
      pool:
        core-size: 8
        max-size: 20
        queue-capacity: 50
        
server:
  tomcat:
    # Tomcat优化
    max-threads: 200
    min-spare-threads: 10
    accept-count: 100
    connection-timeout: 5000ms

四、 常见错误与解决方案

错误1:配置属性名错误

yaml 复制代码
# ❌ 错误:属性名错误
spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver  # 应该是driver-class-name
    url: jdbc:mysql://localhost:3306/db

# ✅ 正确
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db

解决方案:使用IDE的配置提示功能,或查阅官方文档确认属性名。

错误2:配置值类型错误

yaml 复制代码
# ❌ 错误:字符串值被解析为布尔值
myapp:
  enabled: "true"  # 被解析为字符串"true",不是布尔值true

# ✅ 正确
myapp:
  enabled: true

解决方案:确保YAML值的类型正确,布尔值不要加引号。

错误3:Profile配置混乱

yaml 复制代码
# ❌ 错误:Profile配置错误
spring:
  profiles: dev
  datasource:
    url: jdbc:h2:mem:testdb

# ❌ 错误:Profile激活语法错误
spring:
  profiles:
    active: dev,prod  # 应该用逗号分隔

# ✅ 正确写法1:使用application-dev.yml
# ✅ 正确写法2:在命令行激活
# java -jar app.jar --spring.profiles.active=dev,prod

解决方案:遵循Spring Boot的Profile规范,使用正确的配置方式。

错误4:环境变量注入失败

yaml 复制代码
# ❌ 错误:环境变量不存在时无默认值
password: ${DB_PASSWORD}  # 如果DB_PASSWORD不存在,启动失败

# ✅ 正确:提供默认值
password: ${DB_PASSWORD:default_password}

# ✅ 更好:强制要求环境变量
password: ${DB_PASSWORD:?MISSING_DB_PASSWORD}

解决方案:为环境变量提供合理的默认值,或在启动时验证。

五、 Spring Boot 3.2.x 新特性支持

5.1 虚拟线程支持

yaml 复制代码
# Spring Boot 3.2 虚拟线程配置
spring:
  threads:
    virtual:
      enabled: true  # 启用虚拟线程
      
server:
  tomcat:
    threads:
      # 使用虚拟线程
      max: 200
      min-spare: 10
      
# 虚拟线程任务执行器
task:
  execution:
    task-executor-type: virtual_threads

注意 :需要JDK 21+,并在启动时添加--enable-preview参数。

5.2 记录类型(Record)配置绑定

java 复制代码
// JDK 16+ Record类型配置绑定
public record ServerConfig(String host, int port, boolean ssl) {}

@ConfigurationProperties(prefix = "app.server")
public record AppServerConfig(
    @NotBlank String name,
    @Valid List<ServerConfig> servers,
    Duration timeout
) {}

对应配置:

yaml 复制代码
app:
  server:
    name: api-server
    timeout: 30s
    servers:
      - host: server1.example.com
        port: 8080
        ssl: true
      - host: server2.example.com
        port: 8081
        ssl: false

六、 配置验证与测试

6.1 配置验证端点

创建配置验证的REST端点:

ConfigController.java

java 复制代码
package com.example.demo.controller;

import com.example.demo.config.AppConfigProperties;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * 配置验证控制器
 * 用于验证application.yml配置是否正确加载
 * 
 * @author 专栏作者
 * @version 1.0
 * @since 2026-05-04
 */
@Slf4j
@Tag(name = "配置管理", description = "配置验证和管理接口")
@RestController
@RequestMapping("/api/config")
@RequiredArgsConstructor
public class ConfigController {
    
    private final Environment environment;
    private final AppConfigProperties appConfigProperties;
    
    @Operation(summary = "获取应用配置", description = "获取当前应用的所有配置信息")
    @GetMapping("/app")
    public Map<String, Object> getAppConfig() {
        log.debug("获取应用配置");
        
        Map<String, Object> config = new HashMap<>();
        config.put("applicationName", environment.getProperty("spring.application.name"));
        config.put("serverPort", environment.getProperty("server.port"));
        config.put("activeProfiles", environment.getActiveProfiles());
        config.put("customConfig", appConfigProperties.getAllConfig());
        
        return config;
    }
    
    @Operation(summary = "验证数据库配置", description = "验证数据库连接配置")
    @GetMapping("/db")
    public Map<String, Object> getDbConfig() {
        log.debug("获取数据库配置");
        
        Map<String, Object> dbConfig = new HashMap<>();
        dbConfig.put("url", environment.getProperty("spring.datasource.url"));
        dbConfig.put("driverClass", environment.getProperty("spring.datasource.driver-class-name"));
        dbConfig.put("maxPoolSize", environment.getProperty("spring.datasource.hikari.maximum-pool-size"));
        dbConfig.put("minIdle", environment.getProperty("spring.datasource.hikari.minimum-idle"));
        
        return dbConfig;
    }
    
    @Operation(summary = "验证Redis配置", description = "验证Redis连接配置")
    @GetMapping("/redis")
    public Map<String, Object> getRedisConfig() {
        log.debug("获取Redis配置");
        
        Map<String, Object> redisConfig = new HashMap<>();
        redisConfig.put("host", environment.getProperty("spring.data.redis.host"));
        redisConfig.put("port", environment.getProperty("spring.data.redis.port"));
        redisConfig.put("database", environment.getProperty("spring.data.redis.database"));
        redisConfig.put("maxActive", environment.getProperty("spring.data.redis.lettuce.pool.max-active"));
        
        return redisConfig;
    }
    
    @Operation(summary = "获取所有配置", description = "获取所有环境变量和系统属性")
    @GetMapping("/all")
    public Map<String, Object> getAllConfig() {
        log.debug("获取所有配置");
        
        Map<String, Object> allConfig = new HashMap<>();
        
        // 系统属性
        Map<String, String> systemProps = new HashMap<>();
        systemProps.put("java.version", System.getProperty("java.version"));
        systemProps.put("user.dir", System.getProperty("user.dir"));
        systemProps.put("os.name", System.getProperty("os.name"));
        allConfig.put("systemProperties", systemProps);
        
        // 环境变量
        Map<String, String> envVars = new HashMap<>();
        envVars.put("JAVA_HOME", System.getenv("JAVA_HOME"));
        envVars.put("PATH", System.getenv("PATH"));
        allConfig.put("environmentVariables", envVars);
        
        // Spring配置
        Map<String, String> springConfig = new HashMap<>();
        springConfig.put("spring.application.name", environment.getProperty("spring.application.name"));
        springConfig.put("server.port", environment.getProperty("server.port"));
        springConfig.put("spring.profiles.active", String.join(",", environment.getActiveProfiles()));
        allConfig.put("springConfig", springConfig);
        
        return allConfig;
    }
}

访问 /api/config/app 的响应示例

json 复制代码
{
  "applicationName": "spring-boot-demo",
  "serverPort": "8080",
  "activeProfiles": [],
  "customConfig": {
    "config": {
      "version": "1.0.0",
      "uploadPath": "/data/uploads",
      "tempPath": "/tmp"
    },
    "security": {
      "jwt": {
        "secret": "your-jwt-secret-key-change-in-production",
        "expiration": 86400,
        "header": "Authorization",
        "tokenPrefix": "Bearer "
      },
      "encryption": {
        "aesKey": "default-aes-key-change-me"
      }
    },
    "business": {
      "page": {
        "maxSize": 100,
        "defaultSize": 20
      },
      "cache": {
        "userInfoTtl": 300,
        "orderInfoTtl": 600
      },
      "retry": {
        "maxAttempts": 3,
        "backoffDelay": 1000
      }
    }
  }
}

七、 配置文件模板下载

为方便快速开始,这里提供一个完整的配置文件模板:

[application-template.yml]

yaml 复制代码
# ============================================
# Spring Boot 3.2.x Application Configuration Template
# 作者:xxxxxx
# 版本:1.0.0
# 日期:2026-05-04
# ============================================

# 基础配置
server:
  port: 8080
  servlet:
    context-path: /
  compression:
    enabled: true

spring:
  application:
    name: ${APP_NAME:my-application}
  profiles:
    active: @activatedProperties@
  
  # 数据源配置
  datasource:
    url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:appdb}?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
    username: ${DB_USER:root}
    password: ${DB_PASS:password}
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
  
  # Redis配置
  data:
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASS:}
      lettuce:
        pool:
          max-active: 20
  
  # 文件上传
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 20MB

# MyBatis-Plus配置
mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml
  configuration:
    map-underscore-to-camel-case: true

# SpringDoc配置
springdoc:
  api-docs:
    enabled: true
  swagger-ui:
    enabled: true

# 日志配置
logging:
  level:
    root: INFO
    com.example: DEBUG
  file:
    name: logs/${spring.application.name}.log
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"

# 自定义配置
app:
  config:
    version: 1.0.0
  security:
    jwt:
      secret: ${JWT_SECRET:change-me-in-production}
  business:
    page:
      max-size: 100

记住配置管理的黄金法则:配置应该与代码分离,环境特定的配置应该从代码中完全移除

在实际项目中,随着服务规模的扩大,建议考虑使用配置中心(如Nacos、Apollo、Consul)来实现配置的集中管理和动态刷新。

相关资源

相关推荐
SamDeepThinking2 小时前
程序员如何接受工作内容毫无意义?
java·后端·程序员
三翼鸟数字化技术团队2 小时前
基于Redis ZSet实现分布式优先级队列的技术实践
java·redis
无所事事O_o3 小时前
加密过程及原理浅析
java·加密
2301_771717213 小时前
最近在刷牛客:使用Spring AOP实现性能监控时
java·后端·spring
华清远见成都中心3 小时前
C 语言内存管理深度解析:malloc/free 与嵌入式堆栈分配策略
java·c语言·算法
YANZ2223 小时前
亚马逊绿标(CPF):从环保认证到跨境流量新引擎
java·大数据·人工智能·搜索引擎·百度
超梦dasgg3 小时前
智慧充电系统订单服务Java 实现方案
java·开发语言·微服务
JWASX3 小时前
【RocketMQ 生产者和消费者】- 事务源码分析(2)
java·rocketmq·java-rocketmq
手握风云-4 小时前
Spring AI:让大模型住进 Spring 生态(四)
java·后端·spring