非Spring应用如何将.gitlab-ci.yml变量注入application.properties
问题根因
现有配置的核心错误有3点:
- 原生Java Properties无自动占位符替换能力:你在
application.properties中配置的TEST_VARIABLE=${TEST_VARIABLE},通过JDK原生Properties.load()读取时,只会得到字面量字符串${TEST_VARIABLE},不会自动匹配环境变量、JVM参数完成值替换。这类占位符解析是Spring、Apache Commons Configuration等第三方配置框架提供的扩展能力,你的非Spring项目默认没有这个逻辑。 - 静态配置文件不会自动注入CI环境变量:存储在代码仓库
resources目录下的application.properties是静态文件,CI执行gradlew test时,Gradle只会将文件原封不动复制到测试类路径下,不会主动扫描文件内容、把占位符替换为当前CI环境的变量值。 - 资源加载逻辑缺少防御性判断:你的
PropertiesLoader中没有对getResourceAsStream返回的输入流做非空校验,一旦资源路径匹配失败、输入流为null,调用Properties.load(null)就会直接抛出java.lang.IllegalArgumentException,这是你看到报错的直接触发点。另外手动关闭流的写法没有考虑异常场景,容易出现流泄漏。
可行方案
方案1:最小改动适配现有逻辑
保留现有properties配置写法,只修改PropertiesLoader的逻辑,增加占位符解析、非空校验和安全的流处理:
import java.io.IOException; import java.io.InputStream; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; public final class PropertiesLoader { private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{([^}]+)}"); private PropertiesLoader() {} public static String getTestProperty() throws IOException { String rawValue = loadProperties().getProperty("TEST_VARIABLE"); return resolvePlaceholders(rawValue); } private static Properties loadProperties() throws IOException { Properties configuration = new Properties(); // 用try-with-resources自动关闭流,避免泄漏 try (InputStream inputStream = PropertiesLoader.class .getClassLoader() .getResourceAsStream("application.properties")) { if (inputStream == null) { throw new IOException("application.properties 未在类路径中找到"); } configuration.load(inputStream); } return configuration; } // 自定义占位符解析逻辑,优先读取JVM系统参数,其次读取系统环境变量 private static String resolvePlaceholders(String value) { if (value == null) { return null; } Matcher matcher = PLACEHOLDER_PATTERN.matcher(value); StringBuilder sb = new StringBuilder(); while (matcher.find()) { String varName = matcher.group(1); String realVal = System.getProperty(varName, System.getenv(varName)); if (realVal == null) { throw new IllegalArgumentException("无法解析占位符:${" + varName + "}"); } matcher.appendReplacement(sb, Matcher.quoteReplacement(realVal)); } matcher.appendTail(sb); return sb.toString(); } }
修改后不需要调整.gitlab-ci.yml配置,CI环境中已经注入的TEST_VARIABLE会被自动解析读取。
方案2:更简洁的无配置文件方案(推荐)
你仅需要在测试运行阶段读取CI传入的变量,完全不需要通过application.properties做中转,直接在测试代码中读取环境变量即可:
// 测试代码中直接获取 String testVar = System.getenv("TEST_VARIABLE");
这种方式没有额外的配置解析逻辑,出错概率最低。本地开发调试时,只需要在本地环境提前设置对应环境变量,或者在IDE的测试运行配置中添加环境变量即可正常运行。
方案3:通过Gradle测试配置传参
如果希望统一配置入口,可以通过Gradle的test任务把CI环境变量传递为测试JVM的系统参数,不需要在properties中写占位符:
- 删除
application.properties里的TEST_VARIABLE=${TEST_VARIABLE}配置行 - 修改
build.gradle中的test任务配置:test { useJUnitPlatform() // 传递CI环境变量为JVM系统参数,本地运行时使用后面的默认值 systemProperty "TEST_VARIABLE", System.getenv("TEST_VARIABLE") ?: "本地调试默认值" } - 调整
PropertiesLoader的取值逻辑,增加系统参数兜底:public static String getTestProperty() throws IOException { Properties prop = loadProperties(); // 优先读配置文件值,没有则取JVM系统参数 return prop.getProperty("TEST_VARIABLE", System.getProperty("TEST_VARIABLE")); }
变量存储最佳实践
- 你当前把
NONPROD_TEST_VARIABLE存储在GitLab项目CI变量中的方式是合理的,不要将环境相关、尤其是敏感的变量值硬编码到代码仓库的配置文件中。 - 不建议通过Gradle构建阶段替换properties占位符的方式传递敏感变量,这种方式会把变量明文写入编译后的构建产物,存在泄露风险。
- 如果后续需要多环境适配,可以给GitLab CI变量配置对应环境的作用域,无需修改代码就能自动匹配不同环境的变量值。
内容的提问来源于stack exchange,提问作者YourGreatDream
相关产品推荐
相关产品推荐

