大家好,我是凯哥 Java
本文标签:springBoot 启动参数
上周调本地模拟数据中台,IDE 的 Program arguments 配了三回都没生效。换台机器、换个启动方式,又得从头来一遍。后来我把三个参数写进启动类,用一个小方法兜底:命令行给了就用命令行的,没给就用代码里的。从此任何机器、任何启动方式,开箱即用;删 mock 包那天,把注释标记的这段代码一起删掉就行。
这篇文章把三件事说清楚:System.setProperty 这行代码到底在干什么、缺省值模式怎么落地、不写启动类时 java -jar 命令行怎么配。
1. 问题:IDE 的 Program arguments 为什么不牢靠
(1) 参数只挂在当前的 Run Configuration 上
IDEA 里的 Program arguments 不是项目属性,是某个 Run Configuration 的属性。今天在这个配置里填了,明天用 mvn spring-boot:run 启动、或者打 jar 跑、或者同事的机器上 clone 下来,参数就没了,一切重来。
(2) 更隐蔽的:-- 参数进不了 System.getProperty
配了没生效,很多时候不是没填对,是读取方式对不上。Spring Boot 会把 --mock.center.enabled=true 解析进 Environment 的命令行属性源,但不会写进 System.getProperty。代码里如果直接 System.getProperty("mock.center.enabled") 去读,永远读不到这个值。填了等于没填,说的就是这种情况。
2. 方案:启动类里的缺省值
(1) System.setProperty 这行代码在干什么
System.setProperty(key, value) 是往 JVM 的系统属性表里写一个键值对,System.getProperty(key) 再把它读出来。这个表是进程级的:当前 JVM 里的代码都能看到,进程结束就清空。它和启动参数 -D 是同一件事------java -Dmock.center.enabled=true 本质上是 JVM 启动时替你把 setProperty 调好了。Spring 的 Environment 也会把系统属性当成一个属性来源,所以 @Value 同样读得到。
(2) 三行代码落地
java
public static void main(String[] args) {
defaultProp("mock.center.enabled", "true");
defaultProp("mock.userCount", "2600");
defaultProp("steel.baseUrl", "http://127.0.0.1:28099");
SpringApplication.run(HuaweiCenterApplication.class, args);
}
private static void defaultProp(String key, String value) {
if (System.getProperty(key) == null) {
System.setProperty(key, value);
}
}
defaultProp 的逻辑就一行判断:System.getProperty(key) 是 null,说明没人配过,写入缺省值;不是 null,说明命令行已经给了,跳过。这一行判断,就是整个方案的优先级规则。
(3) 为什么放在 main 最前面
main 的第一行代码执行时,-D 参数早就被 JVM 写进系统属性了。把缺省值放在 SpringApplication.run 之前设置,应用上下文启动时属性已经就位,@Value、Environment、常量类都能拿到。
3. 原理:-D、--、代码缺省值,谁说了算
(1) -D(JVM 参数):main 之前就绪
-Dmock.center.enabled=false 由 JVM 在启动时写入系统属性,早于 main。defaultProp 的 null 检查天然放行:属性已存在,不覆盖。它对 System.getProperty 和 Environment 都可见,是最可靠的覆盖方式。
(2) --(Program arguments):进 Environment,不进系统属性
Spring Boot 把 --mock.center.enabled=false 解析为命令行属性源,优先级在系统属性之上;但 System.getProperty 读不到它。只有走 Environment(@Value、Environment.getProperty、配置绑定)才能读到。
(3) 于是,优先级取决于代码怎么读
代码用 @Value 或 Environment 读:-- 能盖过代码缺省值,注释里"命令行优先级高于此处"成立。
代码直接 System.getProperty 读:-- 无效,只能靠 -D 覆盖。
代码缺省值永远兜底:前两者都没给时,用它。
4. 不写启动类:java -jar 命令行怎么配
(1) 用 -D,最直接
不写启动类,就把参数放到 JVM 层:
bash
java -Dmock.center.enabled=true -Dmock.userCount=2600 -jar app.jar
注意 -D 必须放在 -jar 前面。-D 是 JVM 参数,-jar 后面的内容属于程序参数 args,Spring Boot 只会把它当命令行属性处理,不会写进系统属性。
(2) 用 -- 也行,前提是代码走 Environment 读
java -jar app.jar --mock.center.enabled=true 这条等价于在 IDE 里填 Program arguments。代码用 @Value 读没问题;要是直接 System.getProperty 读,这条命令对你的代码无效。
(3) 优先级串起来
按 Spring 属性源顺序:命令行参数 -- 最高,其次是 JVM 系统属性(-D 和代码 set 的都在这层),再到 application.yml。所以即使启动类里写了 defaultProp("mock.center.enabled", "true"),命令行 java -Dmock.center.enabled=false 依然能把它关掉。
(4) 怎么验证
启动后打印一行 System.getProperty("mock.center.enabled") 看看,或者查 Actuator 的 /env 端点,最直观。
5. 实战:2600 个模拟用户,测无 totalSize 的满页翻页
(1) 数据怎么设计
mock.userCount=2600:单页上限 2000 条,第一页 2000 条恰好满页、第二页 600 条不满页,一次覆盖"满页继续翻、不满页停止"两个分支。用户清单和日用电量都会超过单页 2000 条,专门用来踩分页边界。
(2) 为什么这个数值得测
接口不返回 totalSize 时,只能靠"这一页取没取满 2000 条"判断还有没有下一页。这种逻辑最容易在两类边界出错:恰好整除 2000,以及余数页。2600 恰好两个都占:整除点(2000)和余数(600)。
6. 什么时候别用这个模式
(1) 临时测试参数:合适
一行注释 + 三行代码,删包即删参,团队无感知。
(2) 长期配置:交给 profile 和配置中心
application-local.yml、spring.profiles.active、Nacos 才是长期配置的入口;启动类里的参数不该变成正式配置的一部分。
(3) 防止误提交生产
给 mock 开关加环境判断或 @Profile("local") 限定。模拟中台一旦误开进生产,后果得自己收拾。
这套东西不复杂,真正容易栽跟头的是"谁在什么时候写了属性、谁在什么时候读"。IDE 配置没生效、读取方式对不上,这两种坑我都踩过。你要是也遇到过类似的,留言说说你的版本。