Skip to content

启动配置与环境配置

This content is not available in your language yet.

Feat Cloud 里有三类配置容易混在一起:

  • CloudOptions:启动应用时传给 Feat Cloud 的运行时选项
  • feat.yml / feat-{env}.yml:编译期生成代码时读取的配置输入
  • ${...} 占位符:写在 feat.yml 的值里,运行时从系统属性/环境变量取值

这章分两段展开:

  • 前半段讲 CloudOptions:启动入口里本次应用怎么跑
  • 后半段讲 feat.yml:不同环境如何参与编译期生成,以及值如何在运行时注入

核心判断:启动入口里本次怎么跑,看 CloudOptions;会影响生成代码的环境差异,看 feat.yml;要在运行时注入密钥等值,用 ${}

这一部分只处理启动入口里的运行时选项:扫描哪些包、提前注册哪些对象、是否托管静态资源。

CloudOptions 继承自 ServerOptions,所以端口、线程数、调试模式、HTTPS 这类基础能力仍然在 ServerOptions 里配置。

遇到这些问题时,通常看 CloudOptions

  • 控制器已经写好,但你想限制哪些包会被接受
  • 有对象必须在 Feat Cloud 容器启动前就准备好
  • 应用需要顺手托管 classpath 或文件系统里的静态资源
  • 本次启动要临时调整端口、debug 或 Router

遇到这些问题时,通常看 feat.yml 或 Profile:

  • 不同环境使用不同端口、数据库或 Redis
  • Session 存储方式在本地和生产不同
  • MyBatis、Redis 等扩展需要参与编译期生成
  • 修改后需要重新构建才生效
配置项方法典型用途
包扫描setPackages(String...)控制 Controller / Bean 的扫描范围
外部 BeanregisterBean(String, Object)把启动前已有对象注册进容器
静态资源setStaticLocations(String)托管类路径或本地目录里的静态文件

如果你的启动类和控制器不在同一个合理的根包下,或者你只想限制扫描范围,可以显式设置包路径。

Bootstrap.java
import tech.smartboot.feat.cloud.FeatCloud;
public class Bootstrap {
public static void main(String[] args) {
FeatCloud.cloudServer(options -> {
options.setPackages(
"com.example.controller",
"com.example.service"
);
}).listen();
}
}

什么时候应该用它:

  • 多模块项目里,控制器不在启动类同包或子包
  • 你只想扫描少量包,提高启动阶段的可控性
  • 你要避免把不相关 demo 或测试类扫进来

如果启动类本身已经位于合理根包下,很多情况下不需要写 setPackages(...)

有些对象不是通过 Feat Cloud 自己创建的,比如数据源、第三方客户端、配置对象。这时可以在启动时手动注册。

ExternalBeanDemo.java
import tech.smartboot.feat.cloud.FeatCloud;
public class ExternalBeanDemo {
public static void main(String[] args) {
FeatCloud.cloudServer(options -> {
options.registerBean("dataSource", createDataSource());
options.registerBean("buildVersion", "1.0.0");
}).listen();
}
private static Object createDataSource() {
return new Object();
}
}

registerBean(...) 的 key 不能重复。源码里如果发现同名 key,会直接抛 FeatException

重复注册同名 Bean 会失败
options.registerBean("dataSource", ds1);
options.registerBean("dataSource", ds2); // 会失败

Feat Cloud 默认静态资源目录是 classpath:static。如果你项目里有前端页面、上传后的公开文件或静态文档,可以直接改位置。

StaticLocationDemo.java
import tech.smartboot.feat.cloud.FeatCloud;
public class StaticLocationDemo {
public static void main(String[] args) {
FeatCloud.cloudServer(options -> {
options.setStaticLocations("classpath:public");
}).listen();
}
}

你可以使用两种路径形式:

静态资源配置
options.setStaticLocations("classpath:static");
options.setStaticLocations("classpath:public");
options.setStaticLocations("/var/www/static");
options.setStaticLocations("./public");

当你配置静态资源目录后,Feat Cloud 会使用静态资源处理器提供目录首页、常见文件类型识别、Last-Modified 缓存和 404 返回。

实际项目里,这几个配置项经常一起出现:

CloudOptionsDemo.java
import tech.smartboot.feat.cloud.FeatCloud;
public class CloudOptionsDemo {
public static void main(String[] args) {
FeatCloud.cloudServer(options -> {
options.port(8080);
options.debug(true);
options.setPackages("com.example.controller", "com.example.service");
options.registerBean("dataSource", createDataSource());
options.setStaticLocations("classpath:public");
}).listen();
}
private static Object createDataSource() {
return new Object();
}
}

这段代码表达的是本次应用如何启动。它不会替代 feat.yml 的环境配置,也不会改变已经生成好的代码结构。

从这里开始,关注点从“本次启动怎么跑”切到“不同环境如何生成代码”。

简笔手绘卡通风格,中央是默认 feat.yml 配置卡片,分叉到 dev、test、prod 三张环境卡片,环境卡片与默认卡片像透明纸叠加,表现深度合并和环境覆盖,900x383,绿色高亮合并路径

业务应用很少只有一个运行环境。本地开发可能连本机数据库,测试环境要打开更多日志,生产环境则使用独立的数据库、Redis 和端口。

默认配置文件有两种写法:

  • feat.yml
  • feat.yaml

环境配置文件也有两种写法:

  • feat-dev.yml
  • feat-dev.yaml
  • feat-prod.yml
  • feat-prod.yaml

其中 devprod 就是环境名。环境名只能包含字母和数字,例如 devtestprodgray1

一个典型项目可以这样组织资源文件:

src/main/resources
src/main/resources/
├── feat.yml
├── feat-dev.yml
└── feat-prod.yml

feat.yml 放所有环境都共享的默认值:

feat.yml
server:
port: 8080
feat:
datasource:
url: jdbc:mysql://localhost:3306/app
username: app
password: app

feat-dev.yml 只写开发环境要覆盖的内容:

feat-dev.yml
server:
port: 8081
feat:
datasource:
url: jdbc:mysql://localhost:3306/app_dev
username: dev
password: dev

feat.ymlserver: 块并不是所有 ServerOptions 选项都能配。AOT 只会绑定 intString 类型的字段,因此 booleanlong 类字段写在这里不生效。

配置项类型说明
portint监听端口
hostString绑定地址
threadNumint工作线程数
headerLimiterint请求头数量上限
readBufferSize / writeBufferSizeint读写缓冲区大小
autoSSL特殊设为 true 启用自动 HTTPS
session.store-type特殊设为 redis 切换为 Redis 会话存储
session.timeout特殊会话超时时间

这些值同样支持 ${} 占位符,例如 port: ${port}

Feat Cloud 会先读取默认配置,再读取环境配置。

dev 环境来说,顺序是:

  1. 读取 feat.yml
  2. 读取 feat-dev.yml
  3. feat-dev.yml 的内容覆盖到默认配置上

覆盖粒度遵循一个规则:对象深度合并,普通值和数组整体替换。

feat.yml
server:
port: 8080
host: 0.0.0.0
feat-dev.yml
server:
port: 8081

合并后,server.port 会被覆盖,而 host 会继续保留:

dev 环境最终配置
server:
port: 8081
host: 0.0.0.0

如果环境文件里的值是数组,则以环境配置为准,不会和默认数组拼接。

feat.yml 里的值不一定要写死。只要把值写成 ${...} 形式,Feat Cloud 就不会把它当成字面量,而是在运行时从系统属性和环境变量里解析。这正是把敏感信息(数据库口令、Nacos 凭据、第三方 Token)从代码仓库里挪出去的官方做法。

feat.yml
feat:
datasource:
url: jdbc:mysql://localhost:3306/app
username: ${db.username}
password: ${db.password}

usernamepassword 整条值都是 ${...},运行时会解析。打包后,配置文件本身可以安全提交到仓库;真正的密钥在启动时注入:

运行时注入
java -Ddb.username=app -Ddb.password=secret -jar yourapp.jar
# 或者用环境变量
DB_USERNAME=app DB_PASSWORD=secret java -jar yourapp.jar
写法含义
${name}运行时解析 name,找不到则字符串为 null、整数为 0
${name:default}找不到时使用 default 作为字面默认值,按第一个 : 拆分

default 是一段普通文本,不会被再次解析。: 只在第一个出现时作为分隔符,所以含 : 的默认值可以放心写,例如 ${db.url:jdbc:mysql://localhost:3306/app} 的默认值就是整条地址。

feat.yml 里的 ${name} 不是引用另一条配置项,而是去外部取值,顺序是:

  1. JVM 系统属性:名字原样使用,例如 -Ddb.password=secret 匹配 ${db.password}
  2. 操作系统环境变量:名字按“点号转下划线、全大写”规则转换,例如 ${db.password} 对应环境变量 DB_PASSWORD
  3. 都没有时,使用 :default,没有默认值则字符串为 null、整数为 0

${} 只对 Feat Cloud 在编译期消费的配置值生效,典型包括:

  • server.* 启动项,例如 server.port: ${port}
  • feat.datasource.*urlusernamepassworddriver
  • feat.redis.*usernamepassworddatabase
  • @Value("${...}") 注入的字段所引用的配置值

自己用代码直接从配置里读出来的值不会被替换。所以 ${} 应该写在 Feat Cloud 已经接管的那几个配置块里,而不是任意 YAML 键上。

${} 解析发生在运行时,而 feat.profiles.active 选择环境也发生在运行时,但两者互不依赖:每个环境的 feat-{env}.yml 都可以各自使用 ${}。常见做法是把结构性的环境差异(端口、地址)交给 Profile,把同一份配置里需要保密的值交给 ${},配合使用:

feat-prod.yml
feat:
datasource:
url: jdbc:mysql://prod-db.example.com:3306/app
username: ${db.username}
password: ${db.password}

这样生产环境的地址走 Profile 覆盖,凭据走运行时注入,仓库里既看不到口令,也不必为每个环境硬编码账号。Nacos 凭据的同一种用法可以参考 Nacos 接入

如果项目里只有 feat.yml,Feat Cloud 只会生成默认环境的一套代码。

如果同时存在 feat-dev.ymlfeat-prod.yml,Feat Cloud 会为每个环境生成一套完整代码:

配置文件生成类名示例
feat.ymlFeatApplication
feat-dev.ymlFeatApplicationDev
feat-prod.ymlFeatApplicationProd

Controller、Bean、Mapper 等生成类也会追加同样的环境后缀。这样做是为了让每个环境都有独立的配置结果,同时避免生成类同名冲突。

打包后的应用里可能已经包含多套环境代码。启动时通过 feat.profiles.active 选择其中一套:

使用 JVM 参数
java -Dfeat.profiles.active=dev -jar yourapp.jar

也可以使用环境变量:

使用环境变量
FEAT_PROFILES_ACTIVE=prod java -jar yourapp.jar

如果两者都没有设置,Feat Cloud 会使用默认环境,也就是 feat.yml 对应的那套生成结果。

适合拆分的内容通常有这些:

  • 端口
  • 数据库地址
  • Redis 地址
  • 会话存储方式
  • 第三方服务地址
  • 只在某个环境启用的 Feat Cloud 扩展配置

不建议把业务开关、灰度策略或用户级动态规则放进 feat-{env}.yml。这些配置更适合放进数据库、配置中心或业务自己的配置系统,因为它们通常需要运行时调整。

多环境配置影响的是编译期生成结果。如果你修改了 feat-dev.yml,只重启已经打好的 jar 不会生效,需要重新执行构建。

重新生成代码并打包
mvn clean package

这也是 Feat Cloud profile 与传统运行期 profile 最大的区别:它追求的是启动时少扫描、少反射、少动态决策,而不是运行时随时重载配置。