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 架构中,存在几个关键的“陷阱”和“时间差”,使得“等全部配置生效再启动”这个理想状态难以完美实现。
让我们一步步拆解。
- Spring Boot 的启动流程:一个“渐进式”的过程
Spring Boot 的启动不是“原子操作”,而是一个多阶段、渐进式的流程:
深色版本
-
加载 bootstrap 配置 (bootstrap.yml)
↓ -
创建 Bootstrap Context
↓ -
从 Nacos 拉取远程配置 (如 application-dev.yaml)
↓ -
加载 application 配置 (application.yml + 远程配置)
↓ -
创建主 Application Context
↓ -
扫描 @Component, @Service, @Controller ...
↓ -
实例化和初始化 Bean (调用构造函数, @PostConstruct, InitializingBean)
↓ -
发布 ApplicationReadyEvent (应用完全就绪)
↓ -
Tomcat/Jetty 嵌入式服务器正式开始监听端口,接收外部请求
关键点:在步骤 7 (Bean 初始化) 期间,应用已经具备了处理请求的能力,但主流程还在继续。某些 Bean 可能会“提前”触发业务逻辑。 -
“提前”触发请求处理的“陷阱”
即使在主应用上下文完全创建前,也可能有“请求”或“业务操作”发生:
陷阱 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 初始化完成后,集中执行,时机更晚、更可控。