概述
配置已经放到 Nacos 上了,接下来要解决的问题只有一个:微服务启动时怎么把这些远程配置拉下来,并且和本地 application.yml 合并成一份完整的配置。
难点不在"拉",在于"什么时候拉"。拉取远程配置需要先知道 Nacos 地址,而 Nacos 地址本身也是一条配置------如果它写在 application.yml 里,就变成了"要读配置得先读配置"的死循环。本文要讲清楚的就是这个先后顺序,以及围绕它展开的五步改造。
纲要
- 问题背景
- 实例一多,
application.yml散落在几十个服务里 - 改一个公共配置要重新打包、重新发布
- 实例一多,
- 启动流程的变化
- 没有配置中心:读本地配置 → 创建容器 → 加载 Bean
- 有配置中心:读远程配置 → 读本地配置 → 合并 → 创建容器
- 时序图:bootstrap context 为什么必须早于 application context
- 为什么必须是
bootstrap.yml而不是application.yml - 五步接入改造
- 引
spring-cloud-starter-alibaba-nacos-config依赖 - 新建
bootstrap.yml - 写四行关键配置:服务名、profile、Nacos 地址、文件后缀
- 在 Nacos 控制台按 dataId 建配置文件
- 启动服务,用日志确认拉取成功
- 引
- dataId 拼接规则与"没有 profile"的写法
- 版本差异:Boot 2.4+ 之后 bootstrap 默认不加载了,怎么办
- 可复现的验证方式:改 Nacos 的值 → 重启 → 看接口返回
- 与配置热更新的边界
- 实战踩坑清单
- API 速览
问题背景
前面几步做的是把配置上传 到 Nacos:登录控制台,点右上角加号,填 Data ID、Group、配置格式,把 pattern.dateformat=yyyy-MM-dd 这类内容贴进去。上传是纯手工操作,没有代码。
真正有技术含量的是下游:user-service、order-service 这些微服务怎么在启动时把配置取回来。
先看一眼接入前的状态。工程里每个服务的 src/main/resources/application.yml 各写各的:
tree
cloud-demo
├── pom.xml
├── eureka-server
├── feign-api
├── user-service
│ ├── pom.xml
│ └── src/main
│ ├── java/cn/itcast/user
│ │ ├── UserApplication.java
│ │ ├── config/PatternProperties.java
│ │ ├── mapper/UserMapper.java
│ │ ├── pojo/User.java
│ │ ├── service/UserService.java
│ │ └── web/UserController.java
│ └── resources
│ ├── application.yml # 本地配置:端口、数据源、mybatis
│ └── bootstrap.yml # 接入配置中心后新增
├── order-service
└── gateway
问题是可复现的:pattern.dateformat 这个格式化模板,user-service 用一份、order-service 用一份,哪天要统一改成 yyyy/MM/dd,就得挨个改、挨个重新打包上线。实例数到几十上百个的时候,这件事就不可控了。
把变化频繁的配置交给 Nacos 托管,本地只留那些基本不变的东西(数据源地址、端口、mybatis 包扫描),这才是配置中心的正确用法。讲义里也提了一句边界:需要热更新、会频繁变的配置才有放到 Nacos 的必要,万年不动的配置留在本地更省事。
启动流程到底变了什么
没有配置中心的时候
流程很直白:
#mermaid-svg-4ZOhKdiFd640Y0vY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4ZOhKdiFd640Y0vY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4ZOhKdiFd640Y0vY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4ZOhKdiFd640Y0vY .error-icon{fill:#552222;}#mermaid-svg-4ZOhKdiFd640Y0vY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4ZOhKdiFd640Y0vY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4ZOhKdiFd640Y0vY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4ZOhKdiFd640Y0vY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4ZOhKdiFd640Y0vY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4ZOhKdiFd640Y0vY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4ZOhKdiFd640Y0vY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4ZOhKdiFd640Y0vY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4ZOhKdiFd640Y0vY .marker.cross{stroke:#333333;}#mermaid-svg-4ZOhKdiFd640Y0vY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4ZOhKdiFd640Y0vY p{margin:0;}#mermaid-svg-4ZOhKdiFd640Y0vY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4ZOhKdiFd640Y0vY .cluster-label text{fill:#333;}#mermaid-svg-4ZOhKdiFd640Y0vY .cluster-label span{color:#333;}#mermaid-svg-4ZOhKdiFd640Y0vY .cluster-label span p{background-color:transparent;}#mermaid-svg-4ZOhKdiFd640Y0vY .label text,#mermaid-svg-4ZOhKdiFd640Y0vY span{fill:#333;color:#333;}#mermaid-svg-4ZOhKdiFd640Y0vY .node rect,#mermaid-svg-4ZOhKdiFd640Y0vY .node circle,#mermaid-svg-4ZOhKdiFd640Y0vY .node ellipse,#mermaid-svg-4ZOhKdiFd640Y0vY .node polygon,#mermaid-svg-4ZOhKdiFd640Y0vY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4ZOhKdiFd640Y0vY .rough-node .label text,#mermaid-svg-4ZOhKdiFd640Y0vY .node .label text,#mermaid-svg-4ZOhKdiFd640Y0vY .image-shape .label,#mermaid-svg-4ZOhKdiFd640Y0vY .icon-shape .label{text-anchor:middle;}#mermaid-svg-4ZOhKdiFd640Y0vY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4ZOhKdiFd640Y0vY .rough-node .label,#mermaid-svg-4ZOhKdiFd640Y0vY .node .label,#mermaid-svg-4ZOhKdiFd640Y0vY .image-shape .label,#mermaid-svg-4ZOhKdiFd640Y0vY .icon-shape .label{text-align:center;}#mermaid-svg-4ZOhKdiFd640Y0vY .node.clickable{cursor:pointer;}#mermaid-svg-4ZOhKdiFd640Y0vY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4ZOhKdiFd640Y0vY .arrowheadPath{fill:#333333;}#mermaid-svg-4ZOhKdiFd640Y0vY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4ZOhKdiFd640Y0vY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4ZOhKdiFd640Y0vY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4ZOhKdiFd640Y0vY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4ZOhKdiFd640Y0vY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4ZOhKdiFd640Y0vY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4ZOhKdiFd640Y0vY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4ZOhKdiFd640Y0vY .cluster text{fill:#333;}#mermaid-svg-4ZOhKdiFd640Y0vY .cluster span{color:#333;}#mermaid-svg-4ZOhKdiFd640Y0vY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4ZOhKdiFd640Y0vY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4ZOhKdiFd640Y0vY rect.text{fill:none;stroke-width:0;}#mermaid-svg-4ZOhKdiFd640Y0vY .icon-shape,#mermaid-svg-4ZOhKdiFd640Y0vY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4ZOhKdiFd640Y0vY .icon-shape p,#mermaid-svg-4ZOhKdiFd640Y0vY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4ZOhKdiFd640Y0vY .icon-shape .label rect,#mermaid-svg-4ZOhKdiFd640Y0vY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4ZOhKdiFd640Y0vY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4ZOhKdiFd640Y0vY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4ZOhKdiFd640Y0vY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 项目启动
读取本地 application.yml
创建 Spring 容器
扫描并加载各种 Bean
服务就绪
配置只有一个来源,读完之后容器才创建,顺序上没有任何冲突。
有配置中心之后
多了 Nacos 这一份配置,它必须参与"配置合并",也就必须插到"读取本地配置"这一步附近。于是流程变成:
#mermaid-svg-UGvUdFNhlMDaxgbu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UGvUdFNhlMDaxgbu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UGvUdFNhlMDaxgbu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UGvUdFNhlMDaxgbu .error-icon{fill:#552222;}#mermaid-svg-UGvUdFNhlMDaxgbu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UGvUdFNhlMDaxgbu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UGvUdFNhlMDaxgbu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UGvUdFNhlMDaxgbu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UGvUdFNhlMDaxgbu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UGvUdFNhlMDaxgbu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UGvUdFNhlMDaxgbu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UGvUdFNhlMDaxgbu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UGvUdFNhlMDaxgbu .marker.cross{stroke:#333333;}#mermaid-svg-UGvUdFNhlMDaxgbu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UGvUdFNhlMDaxgbu p{margin:0;}#mermaid-svg-UGvUdFNhlMDaxgbu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UGvUdFNhlMDaxgbu .cluster-label text{fill:#333;}#mermaid-svg-UGvUdFNhlMDaxgbu .cluster-label span{color:#333;}#mermaid-svg-UGvUdFNhlMDaxgbu .cluster-label span p{background-color:transparent;}#mermaid-svg-UGvUdFNhlMDaxgbu .label text,#mermaid-svg-UGvUdFNhlMDaxgbu span{fill:#333;color:#333;}#mermaid-svg-UGvUdFNhlMDaxgbu .node rect,#mermaid-svg-UGvUdFNhlMDaxgbu .node circle,#mermaid-svg-UGvUdFNhlMDaxgbu .node ellipse,#mermaid-svg-UGvUdFNhlMDaxgbu .node polygon,#mermaid-svg-UGvUdFNhlMDaxgbu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UGvUdFNhlMDaxgbu .rough-node .label text,#mermaid-svg-UGvUdFNhlMDaxgbu .node .label text,#mermaid-svg-UGvUdFNhlMDaxgbu .image-shape .label,#mermaid-svg-UGvUdFNhlMDaxgbu .icon-shape .label{text-anchor:middle;}#mermaid-svg-UGvUdFNhlMDaxgbu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UGvUdFNhlMDaxgbu .rough-node .label,#mermaid-svg-UGvUdFNhlMDaxgbu .node .label,#mermaid-svg-UGvUdFNhlMDaxgbu .image-shape .label,#mermaid-svg-UGvUdFNhlMDaxgbu .icon-shape .label{text-align:center;}#mermaid-svg-UGvUdFNhlMDaxgbu .node.clickable{cursor:pointer;}#mermaid-svg-UGvUdFNhlMDaxgbu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UGvUdFNhlMDaxgbu .arrowheadPath{fill:#333333;}#mermaid-svg-UGvUdFNhlMDaxgbu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UGvUdFNhlMDaxgbu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UGvUdFNhlMDaxgbu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UGvUdFNhlMDaxgbu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UGvUdFNhlMDaxgbu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UGvUdFNhlMDaxgbu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UGvUdFNhlMDaxgbu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UGvUdFNhlMDaxgbu .cluster text{fill:#333;}#mermaid-svg-UGvUdFNhlMDaxgbu .cluster span{color:#333;}#mermaid-svg-UGvUdFNhlMDaxgbu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UGvUdFNhlMDaxgbu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UGvUdFNhlMDaxgbu rect.text{fill:none;stroke-width:0;}#mermaid-svg-UGvUdFNhlMDaxgbu .icon-shape,#mermaid-svg-UGvUdFNhlMDaxgbu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UGvUdFNhlMDaxgbu .icon-shape p,#mermaid-svg-UGvUdFNhlMDaxgbu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UGvUdFNhlMDaxgbu .icon-shape .label rect,#mermaid-svg-UGvUdFNhlMDaxgbu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UGvUdFNhlMDaxgbu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UGvUdFNhlMDaxgbu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UGvUdFNhlMDaxgbu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 远程 Nacos 配置优先
项目启动
bootstrap context 启动
读取 bootstrap.yml
拿到 Nacos 地址
按三要素拼出 dataId
userservice-dev.yaml
HTTP 拉取 Nacos 配置
并写入 Environment
application context 启动
读取本地 application.yml
同名 key 用哪一份
创建 Bean
完成 @Value / @ConfigurationProperties 注入
服务就绪
看着只是多了两步,但有一个硬约束藏在里面:读取 Nacos 配置之前,必须先知道 Nacos 地址和 dataId 。而地址一旦写在 application.yml 里,就晚了一步------application.yml 是在 application context 阶段才读的,那时候拉远程配置的时机已经过去了。
所以 Spring 才引入了 bootstrap.yml。它由父级 Spring 上下文(bootstrap context)读取,先于 application context 创建,也就是说它的内容在本地 application.yml 生效之前就已经拿到了。
application context Nacos 配置中心 bootstrap context SpringApplication application context Nacos 配置中心 bootstrap context SpringApplication #mermaid-svg-sZqBH2IorKUfbCbz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sZqBH2IorKUfbCbz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sZqBH2IorKUfbCbz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sZqBH2IorKUfbCbz .error-icon{fill:#552222;}#mermaid-svg-sZqBH2IorKUfbCbz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sZqBH2IorKUfbCbz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sZqBH2IorKUfbCbz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sZqBH2IorKUfbCbz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sZqBH2IorKUfbCbz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sZqBH2IorKUfbCbz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sZqBH2IorKUfbCbz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sZqBH2IorKUfbCbz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sZqBH2IorKUfbCbz .marker.cross{stroke:#333333;}#mermaid-svg-sZqBH2IorKUfbCbz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sZqBH2IorKUfbCbz p{margin:0;}#mermaid-svg-sZqBH2IorKUfbCbz .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sZqBH2IorKUfbCbz text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-sZqBH2IorKUfbCbz .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-sZqBH2IorKUfbCbz .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-sZqBH2IorKUfbCbz .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-sZqBH2IorKUfbCbz .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-sZqBH2IorKUfbCbz #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-sZqBH2IorKUfbCbz .sequenceNumber{fill:white;}#mermaid-svg-sZqBH2IorKUfbCbz #sequencenumber{fill:#333;}#mermaid-svg-sZqBH2IorKUfbCbz #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-sZqBH2IorKUfbCbz .messageText{fill:#333;stroke:none;}#mermaid-svg-sZqBH2IorKUfbCbz .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sZqBH2IorKUfbCbz .labelText,#mermaid-svg-sZqBH2IorKUfbCbz .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-sZqBH2IorKUfbCbz .loopText,#mermaid-svg-sZqBH2IorKUfbCbz .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-sZqBH2IorKUfbCbz .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-sZqBH2IorKUfbCbz .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-sZqBH2IorKUfbCbz .noteText,#mermaid-svg-sZqBH2IorKUfbCbz .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-sZqBH2IorKUfbCbz .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sZqBH2IorKUfbCbz .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sZqBH2IorKUfbCbz .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sZqBH2IorKUfbCbz .actorPopupMenu{position:absolute;}#mermaid-svg-sZqBH2IorKUfbCbz .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-sZqBH2IorKUfbCbz .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sZqBH2IorKUfbCbz .actor-man circle,#mermaid-svg-sZqBH2IorKUfbCbz line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-sZqBH2IorKUfbCbz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 先启动父上下文 读取 bootstrap.yml 拿到地址与三要素 拉取 userservice-dev.yaml (DEFAULT_GROUP) 返回 pattern.dateformat 等配置 把远程配置并入 Environment,作为父上下文传入 读取 application.yml 并合并 创建 Bean,完成 @Value 注入
这件事的结论只有一句,但必须记牢:跟 Nacos 地址、配置文件定位有关的所有信息,都放进 bootstrap.yml;本地那些跟远程无关的配置留在 application.yml。
五步接入改造
引依赖:注册中心 starter 不等于配置中心 starter
最容易栽的第一个坑。之前接注册中心时引的是 spring-cloud-starter-alibaba-nacos-discovery,很多人的直觉是"我已经引了 Nacos 的包,配置管理应该也能用了吧"------不能。
两个 starter 管的是两件不相干的事:nacos-discovery 负责服务注册与发现,nacos-config 负责配置的拉取与监听。只引前者,启动时根本不会去找远程配置,也不会报错说少了依赖,就是静默地拉不到。
在 user-service/pom.xml 里补上配置管理依赖:
xml
<!--nacos的配置管理依赖,负责拉取远程配置-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
完整的依赖段长这样,注意 discovery 和 config 都在:
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- nacos 客户端依赖:服务注册与发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- naocs 客户端依赖:配置管理 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
版本不用自己写。父 pom.xml 里通过 dependencyManagement 导入了 spring-cloud-alibaba-dependencies:2.2.5.RELEASE,nacos-config 的版本由它统一管理:
xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
新建 bootstrap.yml:文件名不能改,位置不能错
在 user-service/src/main/resources/ 下新建 bootstrap.yml。
工程里最终是两个文件并列:
tree
user-service/src/main/resources
├── application.yml # 本地配置:端口、数据源、mybatis、日志
└── bootstrap.yml # 引导配置:Nacos 地址 + dataId 三要素
这里有三条硬性约束:
| 约束 | 正确做法 | 写错的后果 |
|---|---|---|
| 文件名 | 必须是 bootstrap |
叫 bootstrap-dev.yml、config.yml 一律不生效 |
| 后缀 | .yml / .yaml 都可以 |
后缀随便,但内容格式要对 |
| 位置 | 必须在 src/main/resources 根目录 |
放到 resources/config/ 下 Spring 扫不到,静默失效 |
工程里用的是 .yml,讲义里写的是 .yaml,两者等价,别在这上面纠结。
写四行内容:三要素 + 地址
bootstrap.yml 里只放跟远程配置定位相关的信息:
yaml
spring:
application:
name: userservice # 服务名称,参与拼 dataId
profiles:
active: dev # 开发环境,参与拼 dataId
cloud:
nacos:
server-addr: localhost:8848 # Nacos 地址
config:
file-extension: yaml # 远程配置文件的后缀名
四个值的含义可以对照着 Nacos 控制台的字段看:
| 配置项 | 作用 | 本例取值 |
|---|---|---|
spring.application.name |
dataId 前缀 | userservice |
spring.profiles.active |
dataId 中间段 | dev |
spring.cloud.nacos.config.file-extension |
dataId 后缀 | yaml |
spring.cloud.nacos.server-addr |
去哪台机器拉 | localhost:8848 |
拼出来的 dataId 就是 userservice-dev.yaml,正好对应控制台里建好的那份配置。
顺带说一个实际部署的差别:讲义里地址是 localhost:8848,而本工程后来容器化时改成了 nacos:8848。原因很直白------服务跑在容器里,容器内的 localhost 指向容器自己,不是宿主机上的 Nacos。这个坑在本地开发时完全看不出来,一上 Docker 就全部连不上。
改完 bootstrap.yml,把 application.yml 里重复的部分注释掉。工程里的 application.yml 保留了端口、数据源、mybatis 和日志,Nacos 相关的东西一行都不留:
yaml
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
# 本地兜底值,Nacos 上同名配置会覆盖它
pattern:
name: 本地环境local
application.yml 里那行 pattern.name: 本地环境local 是故意留的,后面验证优先级时会用到。
在 Nacos 控制台按 dataId 建配置
打开 Nacos 控制台,配置管理 → 配置列表 → 点加号,表单填三项:
| 表单字段 | 填写值 | 说明 |
|---|---|---|
| Data ID | userservice-dev.yaml |
必须和 bootstrap 里拼出来的完全一致,区分大小写 |
| Group | DEFAULT_GROUP |
不填默认就是它,微服务默认也按这个组找 |
| 配置格式 | YAML |
跟 file-extension 对上 |
配置内容:
yaml
pattern:
dateformat: yyyy-MM-dd
name: 来自Nacos的配置
Data ID 的点、横线都要对:userservice-dev.yaml 是"服务名-环境.后缀",中间那个短横线是分隔符,不是随便加的。
启动服务,看日志确认拉取成功
重启 UserApplication。启动过程中 Nacos 配置客户端会打印拉取记录,重点盯这两个关键字:
bootstrapProperties------ 后面跟着的正是从 Nacos 拉到的 dataId,出现bootstrapProperties-userservice-dev.yaml,DEFAULT_GROUP就说明这份配置进 Environment 了Located property source------ 打印本次合并进来的 property source 列表
不同版本的日志文案略有差异,认关键字 bootstrapProperties 加你的 dataId 就够了,别去背整行。反过来说,如果启动日志里完全没有这两个词,那基本可以判定:要么依赖没引对,要么 bootstrap.yml 没在 resources 根目录,要么 dataId 拼错导致 Nacos 那边返回空。
日志确认之后,请求 http://localhost:8081/user/now,返回的是按 yyyy-MM-dd 格式化好的日期,说明远程配置确实生效了。同时起第二个实例(8082 端口、profile 改成 test),也能读到同一份配置。
dataId 的拼接规则
dataId 不是随便起的,它是三个配置项按固定格式拼出来的:
text
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
对照几种情况:
| 服务名 | profile | file-extension | 拼出的 dataId | 备注 |
|---|---|---|---|---|
| userservice | dev | yaml | userservice-dev.yaml |
最常见 |
| userservice | test | yaml | userservice-test.yaml |
多环境并存 |
| userservice | 空 | yaml | userservice.yaml |
见下方说明 |
| userservice | dev | yml | userservice-dev.yml |
后缀必须与 Nacos 上一致 |
| userservice | dev | yaml | userservice-dev.yml |
拼错 → 静默拉不到 |
profile 为空这一行要单独拎出来说。没有配置 spring.profiles.active 时,拼出来的 dataId 是 userservice.yaml------中间不带横线 。写成 userservice-.yaml 会一直拉不到配置,而且 Nacos 客户端不会抛异常,只会静默跳过,日志里也看不出明显错误,属于最难排查的一类问题。
版本差异:Boot 2.4 之后 bootstrap 默认不加载了
本工程是 Spring Boot 2.3.9.RELEASE + Spring Cloud Hoxton.SR10,用 bootstrap.yml 完全可行,按上面五步做完就能跑通。
但从 Spring Boot 2.4 / Spring Cloud 2020.0 开始,规则变了:bootstrap.yml 默认不再被加载 ,引入它需要额外的开关。原因也不难理解------Spring 官方希望淡化 bootstrap context 这个特殊机制,改用 spring.config.import 这种更统一的配置导入方式。
于是就有了两条路。
老写法(本工程用),额外补一个依赖把 bootstrap 支持加回来:
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
新写法(Boot 2.4+ 推荐),不用 bootstrap.yml,直接在 application.yml 里声明要导入哪个配置:
yaml
spring:
application:
name: userservice
profiles:
active: dev
cloud:
nacos:
server-addr: localhost:8848
config:
import:
- nacos:userservice-dev.yaml
两种方式对比:
| 维度 | bootstrap.yml |
spring.config.import |
|---|---|---|
| 适用版本 | Boot 2.4 之前默认支持 | Boot 2.4+ 推荐 |
| 额外依赖 | 2.4+ 需引 spring-cloud-starter-bootstrap |
不需要 |
| 加载时机 | 独立的 bootstrap context,早于 application context | 配置导入机制,仍早于普通配置生效 |
| dataId 拼接 | 由服务名 + profile + 后缀自动拼 | 需手写完整 dataId |
| 排错难度 | 文件名/位置错了会静默失效 | 导入失败会直接抛错,更好定位 |
手写 dataId 那一条要注意:spring.config.import 不会帮你拼 profile,写 nacos:userservice-dev.yaml 就固定读这一份,多环境要用 nacos:${spring.application.name}-${spring.profiles.active}.yaml 这类占位符自己拼。
如果在新版本里只是把 bootstrap.yml 原样搬过去、却没引 spring-cloud-starter-bootstrap,现象是:服务能启动,但所有期望从 Nacos 来的配置全部取不到,@Value 注入直接报 Could not resolve placeholder。看到这个报错先去查依赖,别急着改配置内容。
可复现的验证:改 Nacos 的值,重启,看结果
光说"拉到了"不够,得能证伪。用 @Value 注入一个 Nacos 上的配置项,打印出来。
java
package cn.itcast.user.web;
import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 这一行来自 Nacos 的 userservice-dev.yaml
@Value("${pattern.dateformat}")
private String dateformat;
// 本地 application.yml 也有 pattern.name,Nacos 上也有,用它验证优先级
@Value("${pattern.name}")
private String patternName;
@GetMapping("now")
public String now() {
log.info("dateformat = {}, pattern.name = {}", dateformat, patternName);
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id,
@RequestHeader(value = "Truth", required = false) String truth) {
System.out.println("truth: " + truth);
return userService.queryById(id);
}
}
工程里最终用的是 @ConfigurationProperties 这种更规整的写法,一个前缀管一批属性,省得在 Controller 里堆 @Value:
java
package cn.itcast.user.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
private String name;
}
配套的接口:
java
package cn.itcast.user.web;
import cn.itcast.user.config.PatternProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private PatternProperties properties;
@GetMapping("prop")
public PatternProperties properties() {
return properties;
}
@GetMapping("now")
public String now() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(properties.getDateformat()));
}
}
验证步骤,三步走完就能确认:
- 启动服务,访问
http://localhost:8081/user/now,拿到2021-04-12这种格式,说明 Nacos 上的pattern.dateformat生效了 - 访问
http://localhost:8081/user/prop,看name字段的值。本地application.yml写的是本地环境local,Nacos 上写的是来自Nacos的配置,返回后者说明远程配置覆盖了本地同名配置 - 回控制台把
dateformat改成yyyy/MM/dd,刷新页面------格式没变 ,还是老样子;重启服务,再刷新------变成2021/04/12了
第 3 步的"没变"正是本文的边界。讲的是"启动时拉取",@Value 注入的值在 Bean 创建那一刻就定死了,Nacos 上改了也不会自动重刷。要让改动不重启就生效,得靠 @RefreshScope 或 @ConfigurationProperties 配合 Nacos 的配置监听------那是配置热更新的内容,本文不展开。反过来,重启后值跟着变,也就彻底证明了这份配置确实是从 Nacos 来的,而不是本地某处残留的同名配置。
实战踩坑清单
| 现象 | 原因 | 排查动作 |
|---|---|---|
启动正常,但 @Value 报 Could not resolve placeholder |
漏引 spring-cloud-starter-alibaba-nacos-config |
检查 pom 里是不是只有 nacos-discovery |
| 同上,且依赖齐全 | bootstrap.yml 放错位置或文件名写错 |
确认在 src/main/resources 根目录、文件名就是 bootstrap.yml |
| 同上,位置也对 | Boot 2.4+ 没引 spring-cloud-starter-bootstrap |
补依赖,或改用 spring.config.import |
| 配置完全拉不到,日志无任何报错 | dataId 拼错 | 拿 服务名-profile.后缀 逐字符比对控制台里的 Data ID |
| 同上 | profile 为空时多写了一个横线,写成 userservice-.yaml |
删掉横线,直接用 userservice.yaml |
| 同上 | file-extension: yml,但 Nacos 上建的是 userservice-dev.yaml |
两边后缀统一,yaml 就都写 yaml |
| 本地跑通,容器里连不上 Nacos | server-addr 写成 localhost:8848,容器内 localhost 是它自己 |
改成宿主机 IP 或 compose 里的服务名,如 nacos:8848 |
| 本地配置没被覆盖 | 同名配置写在了 bootstrap.yml 里 |
把本地业务配置挪回 application.yml |
这张表里最需要警惕的是静默失败:依赖漏引、dataId 拼错、文件位置不对,这三种情况服务都能正常启动,日志里也不会打红色报错,只有业务代码取配置时才暴露。所以接入完一定要按前面的验证方式实际读一次配置,别看到服务起来了就当成功了。
API 速览
| 类型 | 名称 | 作用 |
|---|---|---|
| 依赖 | spring-cloud-starter-alibaba-nacos-config |
配置中心的客户端 starter,负责拉取与监听远程配置 |
| 依赖 | spring-cloud-starter-bootstrap |
Boot 2.4+ 下恢复 bootstrap.yml 加载 |
| 配置 | spring.cloud.nacos.server-addr |
Nacos 服务地址 |
| 配置 | spring.cloud.nacos.config.file-extension |
远程配置文件后缀,参与拼 dataId |
| 配置 | spring.config.import: nacos:xxx.yaml |
新版本替代 bootstrap.yml 的导入写法 |
| 注解 | @Value("${key}") |
按 key 注入单个配置项 |
| 注解 | @ConfigurationProperties(prefix = "pattern") |
按前缀批量绑定到 POJO |
| 约定 | ${name}-${profile}.${ext} |
dataId 拼接规则,profile 为空时省略横线 |
本文涉及的完整流程:
#mermaid-svg-omwXlBSIYOPwNJMH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-omwXlBSIYOPwNJMH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-omwXlBSIYOPwNJMH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-omwXlBSIYOPwNJMH .error-icon{fill:#552222;}#mermaid-svg-omwXlBSIYOPwNJMH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-omwXlBSIYOPwNJMH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-omwXlBSIYOPwNJMH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-omwXlBSIYOPwNJMH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-omwXlBSIYOPwNJMH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-omwXlBSIYOPwNJMH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-omwXlBSIYOPwNJMH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-omwXlBSIYOPwNJMH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-omwXlBSIYOPwNJMH .marker.cross{stroke:#333333;}#mermaid-svg-omwXlBSIYOPwNJMH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-omwXlBSIYOPwNJMH p{margin:0;}#mermaid-svg-omwXlBSIYOPwNJMH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-omwXlBSIYOPwNJMH .cluster-label text{fill:#333;}#mermaid-svg-omwXlBSIYOPwNJMH .cluster-label span{color:#333;}#mermaid-svg-omwXlBSIYOPwNJMH .cluster-label span p{background-color:transparent;}#mermaid-svg-omwXlBSIYOPwNJMH .label text,#mermaid-svg-omwXlBSIYOPwNJMH span{fill:#333;color:#333;}#mermaid-svg-omwXlBSIYOPwNJMH .node rect,#mermaid-svg-omwXlBSIYOPwNJMH .node circle,#mermaid-svg-omwXlBSIYOPwNJMH .node ellipse,#mermaid-svg-omwXlBSIYOPwNJMH .node polygon,#mermaid-svg-omwXlBSIYOPwNJMH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-omwXlBSIYOPwNJMH .rough-node .label text,#mermaid-svg-omwXlBSIYOPwNJMH .node .label text,#mermaid-svg-omwXlBSIYOPwNJMH .image-shape .label,#mermaid-svg-omwXlBSIYOPwNJMH .icon-shape .label{text-anchor:middle;}#mermaid-svg-omwXlBSIYOPwNJMH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-omwXlBSIYOPwNJMH .rough-node .label,#mermaid-svg-omwXlBSIYOPwNJMH .node .label,#mermaid-svg-omwXlBSIYOPwNJMH .image-shape .label,#mermaid-svg-omwXlBSIYOPwNJMH .icon-shape .label{text-align:center;}#mermaid-svg-omwXlBSIYOPwNJMH .node.clickable{cursor:pointer;}#mermaid-svg-omwXlBSIYOPwNJMH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-omwXlBSIYOPwNJMH .arrowheadPath{fill:#333333;}#mermaid-svg-omwXlBSIYOPwNJMH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-omwXlBSIYOPwNJMH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-omwXlBSIYOPwNJMH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-omwXlBSIYOPwNJMH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-omwXlBSIYOPwNJMH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-omwXlBSIYOPwNJMH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-omwXlBSIYOPwNJMH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-omwXlBSIYOPwNJMH .cluster text{fill:#333;}#mermaid-svg-omwXlBSIYOPwNJMH .cluster span{color:#333;}#mermaid-svg-omwXlBSIYOPwNJMH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-omwXlBSIYOPwNJMH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-omwXlBSIYOPwNJMH rect.text{fill:none;stroke-width:0;}#mermaid-svg-omwXlBSIYOPwNJMH .icon-shape,#mermaid-svg-omwXlBSIYOPwNJMH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-omwXlBSIYOPwNJMH .icon-shape p,#mermaid-svg-omwXlBSIYOPwNJMH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-omwXlBSIYOPwNJMH .icon-shape .label rect,#mermaid-svg-omwXlBSIYOPwNJMH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-omwXlBSIYOPwNJMH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-omwXlBSIYOPwNJMH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-omwXlBSIYOPwNJMH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 引 nacos-config 依赖
新建 bootstrap.yml
写服务名 / profile / 地址 / 后缀
Nacos 控制台建配置
userservice-dev.yaml
启动服务
日志出现 bootstrapProperties
代码读取配置验证
官方文档
- Nacos 配置管理(Spring Cloud Alibaba)
- Spring Cloud Config - Bootstrap Context
- Spring Boot 2.4 Release Notes - Config Data Import
总结
配置拉取这件事的复杂度全在加载顺序上。bootstrap.yml 不是"另一个配置文件",它代表的是一个单独的父上下文,存在的唯一理由是让"Nacos 地址"这类信息在本地配置生效之前就可用。把这层因果关系想清楚,后面遇到的新版本差异、spring.config.import 写法,都只是换了个装载入口,逻辑没变。
接入动作本身只有五步:引 config starter、建 bootstrap.yml、配四项、在 Nacos 建同名 dataId、启动看日志。真正花时间的是排错------静默失败的三类原因(依赖、dataId、文件位置)都不会报错,只能靠日志关键字和实际读一次配置来确认。
本文止步于"启动时拉取",改 Nacos 上的值必须重启才生效。下一步是让改动不重启就生效,也就是配置热更新。