springboot的配置文件

bootstrap配置文件

spring:
  application:
    name: # 👈 必须!服务在 Nacos 中的唯一标识
  profiles:
    active: dev # 设置默认环境  推荐在application中配置
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848 # 👈 必须!Nacos Server 的地址
        file-extension: yaml       # 👈 必须!配置文件格式 (yaml 或 properties)
        # namespace: your-dev-namespace-id # 可选:指定命名空间ID (如 dev)
        # group: DEFAULT_GROUP # 可选分组
        # shared-configs: # 可选共享配置
        # extension-configs: # 可选扩展配置

**shared-configs: **当多个微服务(例如 order-service, user-service, payment-service)都需要使用完全相同的一套配置时(比如统一的日志格式、监控埋点、公共的第三方 SDK 配置、基础的安全规则等),您可以把这些公共配置提取出来,放到一个独立的 Data ID 中(如 common-config.yaml),然后让所有服务通过 shared-configs 引用它。
eg:

# 👇 声明需要加载的共享配置
        shared-configs:
          - data-id: common-config.yaml       # 必填:共享配置的 Data ID
            # group: DEFAULT_GROUP # 可选:指定 Group,默认DEFAULT_GROUP
            # refresh: true                  # 可选:是否支持动态刷新,默认 false
          - data-id: oss-config.yaml         # 可以声明多个
            group: ALIYUN_GROUP
            refresh: true

extension-configs:当一个微服务的配置非常庞大和复杂时(比如包含了数据库、Redis、MQ、短信、邮件、各种业务开关等),为了便于管理和维护,可以将这些配置拆分到多个独立的 Data ID 文件中。例如:
*order-service-db.yaml (数据库配置)
*order-service-redis.yaml (缓存配置)
*order-service-mq.yaml (消息队列配置)
*order-service-sms.yaml (短信配置)
eg:

# 👇 声明需要加载的扩展配置 (优先级较高)
        extension-configs:
          - data-id: order-service-db.yaml
            group: DEFAULT_GROUP
            refresh: true # 数据库连接池参数可能需要动态调整
          - data-id: order-service-redis.yaml
            refresh: true
          - data-id: order-service-business.yaml # 业务开关
            refresh: true

spring.profiles.active: dev 设置后会找配置为spring.application.name中的配置文件名称your-service-name-prod+后缀 -dev即:your-service-name-dev.ymal配置文件。若没有设置这默认加载顺序为:
*your-service-name-prod.yaml (最高优先级)
*your-service-name.yaml
*application-prod.yaml
*application.yaml (最低优先级)

为什么推荐在 application.yml 中配置?

核心原则:bootstrap.yml 负责 “连接配置中心” 的元信息,而 application.yml 负责 “应用自身” 的配置。

# application.yml (Nacos 场景下的典型内容)
spring:
  profiles:
    active: dev # 👈 唯一或最主要的配置

# 其他极少数本地化配置(可选)
server:
  port: 8080 # 如果所有环境端口都一样,可以放这里
logging:
  level:
    com.example: DEBUG # 开发时的本地调试开关 默认info

logger.info("用户登录成功") -> 会输出。
logger.debug("查询数据库的SQL是: SELECT * FROM user WHERE id=1") -> 不会输出。

序列化配置对象转json 反序列化json转对象

spring:
  jackson:
    # 日期格式和时区
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: Asia/Shanghai
    
    # 命名策略:Java 驼峰 -> JSON 蛇形
    property-naming-strategy: SNAKE_CASE
    
    # 序列化:不包含 null 值字段
    default-property-inclusion: NON_NULL
    
    # 反序列化:忽略未知字段,避免报错
    deserialization:
      FAIL_ON_UNKNOWN_PROPERTIES: false

bootstrap.yml先启动 application.yml后启动

没有bootstrap.yml就会加载本地的application.yml,不知道远程有配置。

不使用bootstrap的方法

*1.但从 Spring Boot 2.4+ 开始,可以通过 application.yml 中的 spring.config.import 直接导入远程配置

# application.yml (无需 bootstrap.yml)
spring:
  application:
    name: your-service-name
  config:
    import: 
      - nacos:your-service-name?serverAddr=127.0.0.1:8848&file-extension=yaml&namespace=dev-namespace-id
  profiles:
    active: dev

*2.使用其他配置导入方式

写在 application.yml:配置是静态的、本地的、难以集中管理的。修改配置需要重新打包或修改本地文件。
写在 Nacos:配置是动态的、远程的、可集中管理的。修改配置无需重启应用(如果支持刷新)。

为什么很多单体项目“有” bootstrap.yml?

因为即使是一个单体应用,开发者也可能希望:

*使用 Nacos 管理配置:实现配置的集中化、动态刷新、环境隔离。
*使用 Spring Cloud Alibaba Nacos Discovery:即使不拆微服务,也可能用 Nacos 做服务注册(方便未来拆分或做网关路由)。
*使用 Spring Cloud 的其他组件:如 Sentinel(流量控制)、Seata(分布式事务),这些组件的配置也需要早期加载。

“单体项目中使用了 Nacos、Spring Cloud Config 等配置中心,那么 bootstrap.yml(或其现代替代方案)就变得必要或推荐。”

因为bootstrap.yml 的存在与否,取决于是否使用了需要“早期初始化”的外部化配置源,而不是应用的架构是单体还是微服务。

1. 为什么 Sentinel 、Seata需要早期加载?

场景:Web 请求处理
请求到达:一个 HTTP 请求打到您的 @RestController。
期望行为:
*第一步:Sentinel 拦截请求,根据预加载的规则判断是否限流。
*第二步:如果通过,则交给 Spring MVC 的 DispatcherServlet 处理。
如果配置在 application.yml:
application.yml 在 bootstrap.yml 之后加载。
加载 application.yml 时,Spring 容器已经开始注册 Controller。
风险:在 application.yml 完全加载、Sentinel 规则初始化完成之前,如果有请求进来,Sentinel 可能处于“无规则”状态,导致限流保护失效,应用可能被瞬间打垮。

既然 application.yml 里的配置最终都会被加载,为什么不能等所有配置都生效、所有 Bean 都初始化完、服务完全启动后,再让应用接收请求呢?这样 Sentinel 和 Seata 不就也能准备好了吗?

这个想法在理想情况下是成立的,但在现实的 Spring Boot/Spring Cloud 架构中,存在几个关键的“陷阱”和“时间差”,使得“等全部配置生效再启动”这个理想状态难以完美实现。

让我们一步步拆解。

  1. Spring Boot 的启动流程:一个“渐进式”的过程
    Spring Boot 的启动不是“原子操作”,而是一个多阶段、渐进式的流程:

深色版本

  1. 加载 bootstrap 配置 (bootstrap.yml)

  2. 创建 Bootstrap Context

  3. 从 Nacos 拉取远程配置 (如 application-dev.yaml)

  4. 加载 application 配置 (application.yml + 远程配置)

  5. 创建主 Application Context

  6. 扫描 @Component, @Service, @Controller ...

  7. 实例化和初始化 Bean (调用构造函数, @PostConstruct, InitializingBean)

  8. 发布 ApplicationReadyEvent (应用完全就绪)

  9. Tomcat/Jetty 嵌入式服务器正式开始监听端口,接收外部请求
    关键点:在步骤 7 (Bean 初始化) 期间,应用已经具备了处理请求的能力,但主流程还在继续。某些 Bean 可能会“提前”触发业务逻辑。

  10. “提前”触发请求处理的“陷阱”
    即使在主应用上下文完全创建前,也可能有“请求”或“业务操作”发生:

陷阱 1:@PostConstruct 方法中的数据库操作

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @PostConstruct // 👈 在 Bean 初始化时执行
    public void init() {
        // 这个方法在 Application Context 初始化过程中就会被调用!
        User admin = userRepository.findByUsername("admin");
        if (admin == null) {
            // 创建默认管理员
            userRepository.save(new User("admin", "xxx"));
            // ❗️ 这是一个数据库写操作!
        }
    }
}

时间点:这个 @PostConstruct 方法在 ApplicationReadyEvent 之前执行。
问题:如果此时 Seata 尚未完全初始化(因为它的配置还在 application.yml 里,加载稍晚),那么这次 save 操作可能不会被纳入全局事务管理,导致数据一致性风险。

陷阱 2:CommandLineRunner / ApplicationRunner

@Component
public class DataLoader implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        // 这个方法在所有 Bean 初始化后,ApplicationReadyEvent 前执行
        // ❗️ 可能会进行数据库操作或调用外部服务
        dataImportService.importInitialData();
    }
}

陷阱 3:@EventListener 监听早期事件

@Component
public class EarlyEventListener {
    @EventListener
    public void handleContextRefresh(ContextRefreshedEvent event) {
        // ContextRefreshedEvent 在 ApplicationReadyEvent 之前发布
        // ❗️ 可能会触发一些业务逻辑
        notificationService.sendStartupAlert();
    }
}

陷阱 4:Actuator 端点或管理接口

某些管理操作可能在主流程完成前就可通过 Actuator 端点触发。

3. 为什么 bootstrap.yml 能解决这个问题?

bootstrap.yml 的魔力在于它提前了关键组件的初始化时机。

在 Application Context 创建之前,Bootstrap Context 就已经:
从 bootstrap.yml 读取了 Nacos 地址。
连接到 Nacos,拉取了 application-dev.yaml。
同时,也拉取了 Sentinel 规则、Seata 的 registry/config 配置。
将这些配置“注入”到即将创建的主 Application Context 中。
结果:当主 Application Context 开始创建 Bean 时,Sentinel 和 Seata 的配置已经是可用状态。它们的 BeanPostProcessor、DataSourceProxy、GlobalTransactionScanner 等组件可以在 Bean 初始化的最早期就介入,确保 @PostConstruct 中的数据库操作也能被正确拦截和管理。
4. “全部配置生效再启动”为什么不够完美?
“全部配置生效”是一个模糊概念:是指 application.yml 加载完?还是指所有 Bean 的 @PostConstruct 执行完?还是 ApplicationReadyEvent 发布?
Spring 没有提供“阻塞直到所有配置完全就绪”的标准机制。@DependsOn 只能解决 Bean 之间的依赖,无法解决“配置源”到“组件”的依赖。
分布式配置中心有延迟:从 Nacos 拉取配置、解析、应用,本身需要时间。如果这个过程在 application.yml 阶段才开始,那么在 @PostConstruct 执行时,配置可能还在路上。
“服务已启动”的定义:嵌入式服务器(Tomcat)的启动和 Spring 应用上下文的启动是两个相关但不完全同步的过程。在 Spring 上下文完全初始化前,Tomcat 可能已经绑定端口。
5. 现代方案:spring.config.import 并未改变本质
即使使用 spring.config.import,其工作原理也是:

在 application.yml 加载的非常早期,就解析 import: nacos:...。
立即去连接 Nacos 拉取配置。
将拉取到的配置合并到当前的配置环境中。
这本质上还是实现了“早期加载”,只是不再需要一个独立的 bootstrap 上下文,而是将“导入外部配置”作为 application 配置加载流程的第一步。

结论:bootstrap.yml 是一种“防御性编程”设计
您的想法(等全部配置生效再启动)是理想且正确的。bootstrap.yml(或 spring.config.import)正是为了逼近这个理想状态而存在的。

它通过将配置获取的时机极大提前,确保了:

关键治理组件(Sentinel, Seata)能在任何业务逻辑执行前就位。
即使有 @PostConstruct、CommandLineRunner 等“早期执行”的代码,也能受到保护。
应用从启动的第一毫秒起,就处于一个“受控”和“安全”的状态。
所以说,bootstrap.yml 不是多余的,而是一种确保“初始化顺序正确”和“系统稳定性”的关键设计模式。它解决了“配置加载时机”与“组件初始化时机”之间的微妙时间差问题。

CommandLineRunner 和 ApplicationRunner都是为了用命令启动时携带参数,根据解析参数处理相应逻辑。

java -jar my-spring-boot-app.jar arg1 arg2 arg3 --input=file.txt --debug
@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 1. 检查是否存在 --debug 选项
        boolean  = args.containsOption("debug");
        if(hasDebug) System.setProperty("logging.level.root", "DEBUG");
        List<String> nonOptionArgs = args.getOptionValues("input");
        String inputFile = nonOptionArgs.isEmpty() ? null : nonOptionArgs.get(0);
        // 启动数据处理任务
        dataService.process(inputFile);
    }
}

spring本身也会消费--开头参数,不认识的才会在CommandLineRunner 或 ApplicationRunner 的 args 中!

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

CommandLineRunner 和 ApplicationRunner 是 Spring Boot 中两个非常重要的接口,用于在应用启动后执行一些初始化任务。它们非常相似,但存在一个关键区别。

让我们来详细对比它们的区别和作用。

共同点:核心目的
目的:在 Spring Boot 应用的 ApplicationContext 完全加载之后、ApplicationReadyEvent 发布之前,执行一些自定义的、一次性的初始化逻辑。
执行时机:
ApplicationContext 初始化完成。
所有 Bean 都已创建并初始化。
在 ApplicationReadyEvent 事件发布之前。
嵌入式服务器(如 Tomcat)可能已经开始监听端口。
典型用途:
预热缓存(从数据库加载热点数据到 Redis)。
初始化数据库(插入初始数据、创建索引)。
启动后台监控线程。
调用外部系统进行注册或通知。
执行数据迁移脚本。
打印启动完成日志或系统信息。

与 @PostConstruct 的区别:

@PostConstruct:在单个 Bean 初始化后立即执行,可能有多个,执行时机较早且分散。
Runner:在所有 Bean 初始化完成后,集中执行,时机更晚、更可控。

posted @ 2025-08-26 18:06  chahune  阅读(19)  评论(0)    收藏  举报