Maven配置加载:动态替换的艺术

一句话总结本章的内容:本文介绍了Maven配置文件加载属性的起源(为解决手动管理多环境配置的痛点)、本质(将不变模板与可变环境信息分离的动态替换机制)、实现步骤(定义属性、开启资源过滤)和核心原理(process-resources阶段通过属性内存空间替换占位符),并用印章和明信片的比喻形象解释了这一过程,最后补充了版本管理中SNAPSHOT与RELEASE的区别。

前言:问题的出现或者说是配置文件加载属性的萌芽。

|-----------------------------------------------------------------------------------|
| 当代码需要提交到Git仓库,或者交给测试、部署到生产环境时,会发生什么: 测试和生产环境的数据库地址、账号密码完全不一样。这段代码在其他环境根本跑不起来。 |
| 解决办法: 每次部署前,都需要手动修改这个Java文件,改成对应环境的配置。然后重新编译、打包。 |
| 后果: 这是一个极易出错且耗时费力的过程,而且存在把测试环境的配置错误地带到生产环境的巨大风险。 |
[原始的项目]

伴随着Maven的出现,Maven作为一款优秀的项目构建工具,看到了这个普遍存在的痛点。它想解决的问题是:如何让构建过程自动化,并且能智能地适应不同环境。 它的核心思路就是:**将"不变的模板"和"可变的环境信息"彻底分离。**因此为了解决软件开发中日益严重的环境割裂和手动管理混乱的问题,配置文件加载属性应运而生。

配置文件加载属性是什么:

在Maven中,配置文件加载属性本质上是一种将构建过程中的配置信息集中管理和动态替换的机制 。它的核心目的是将那些因环境(开发、测试、生产)不同或频繁变动的值(如数据库连接、依赖版本)抽离出来,用一个可变的"占位符"(${property.name})代替,从而实现配置与代码的解耦。

简单的说,它就像设计一张"填空题"试卷,而试卷本身(代码)是固定的,具体的"答案"(配置值)则可以在不同场景下灵活填入。

配置文件加载属性的步骤:

第一步:在pom文件中统一管理配置文件中的属性

复制代码
<project>
    <!-- 第一步:在properties中定义属性 -->
    <properties>
        <spring.version>5.2.10.RELEASE</spring.version>
        <junit.version>4.12</junit.version>
    </properties>

    <dependencies>
        <!-- 第二步:在需要使用的地方引用属性 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <!-- 使用 ${属性名} 引用 -->
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
        </dependency>
    </dependencies>
</project>

第二步:将属性值动态注入外部配置文件

这是配置文件加载属性的精髓。它能让我们的jdbc.propertieslog4j.properties等配置文件也使用Maven中定义的属性。

假设你有一个 src/main/resources/jdbc.properties 文件,内容如下:

++properties++

复制代码
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=${jdbc.url}  <-- 这是一个占位符,等待Maven填充
jdbc.username=root
jdbc.password=123456
步骤一:在pom文件中定义属性:

++xml++

复制代码
\<properties\>
\<!-- 定义一个名为 jdbc.url 的属性,值可以根据环境灵活改变 --\>
\<jdbc.url\>jdbc:mysql://localhost:3306/mydb_dev\</jdbc.url\>
\</properties\>
步骤二:开启资源过滤

需要在pom.xml<build>标签中,配置资源插件,告诉Maven在打包时对指定目录(如 src/main/resources)下的文件进行"过滤"。过滤的过程就是解析文件中的${...}占位符,并用定义好的属性值进行替换

复制代码
\<build\>
\<resources\>
\<resource\>
\<!-- 指定要处理的资源目录 --\>
\<directory\>src/main/resources\</directory\>
\<!-- 开启过滤,这是实现属性替换的关键! --\>
\<filtering\>true\</filtering\>
\</resource\>
\</resources\>
\</build\>

最后一步

完成以上配置后,执行mvn process-resources或打包命令,最终生成的jdbc.properties文件中的jdbc.url就会被自动替换为jdbc:mysql://localhost:3306/mydb_dev

形象的说:

我们在这里做一个比喻,把配置文件比做成明信片模板 ,上面有一些固定格式 (也就是占位符),根据不同的人,具体的在里面填写对应的数据。而这些数据是在pom.xml文件中注册的(不同的用户有不同的数据,相当于身份证),同时还在pom.xml文件中写到要把对应的数据填写到配置文件properties中(也就是资源过滤器)。执行 mvn package 等命令,并触发资源过滤功能,将数据填充到占位符中。

资源过滤器详解:

我们可以把资源过滤器比做成印章,而properties文件中的占位符就是等待被填充的印子,而后执行打包命令时,

compile → process-resources → compile → test → package

就在这个阶段!

process-resources阶段的具体操作

当Maven执行到process-resources阶段时:

复制代码
// 伪代码:Maven内部是这样工作的
public void processResources() {
    // 1. 读取所有配置的属性
    Properties mavenProperties = new Properties();
    mavenProperties.put("db.url", "jdbc:mysql://localhost:3306/mydb");
    mavenProperties.put("db.username", "root");
    mavenProperties.put("db.password", "123456");
    
    // 2. 找到所有需要过滤的资源文件
    File resourceFile = new File("src/main/resources/application.properties");
    
    // 3. 读取文件内容
    String content = readFile(resourceFile);
    // content = "数据库地址:${db.url}\n用户名:${db.username}\n密码:${db.password}"
    
    // 4. 替换所有占位符
    for (String key : mavenProperties.keySet()) {
        String placeholder = "${" + key + "}";
        String value = mavenProperties.get(key);
        content = content.replace(placeholder, value);
    }
    
    // 5. 写入到target目录
    writeFile("target/classes/application.properties", content);
}

关键:为什么能访问到pom.xml里的值?

Maven在运行时会维护一个属性内存空间,包含:

复制代码
// Maven的内存模型
Map<String, String> mavenProjectProperties = new HashMap<>();

// 从pom.xml加载的<properties>
mavenProjectProperties.put("db.url", "jdbc:mysql://localhost:3306/mydb");
mavenProjectProperties.put("project.version", "1.0.0");
mavenProjectProperties.put("project.build.sourceEncoding", "UTF-8");

// 系统属性
mavenProjectProperties.put("basedir", "/Users/xxx/my-project");
mavenProjectProperties.put("java.version", "1.8");

// 环境变量
mavenProjectProperties.put("env.JAVA_HOME", "/usr/lib/jvm/java-8");

版本管理

工程版本:

SNAPSHOT(快照版本)

项目开发过程中临时输出的版本,称为快照版本

快照版本会随着开发的进展不断更新

RELEASE(发布版本)

项目开发到进入阶段里程碑后,向团队外部发布较为稳定的版本,这种版本所对应的构件文件是稳定的,即便进行功能的后续开发,也不会改变当前发布版本内容,这种版本称为发布版本

发布版本alpha版,beta版,纯数字版

最后给大推荐如果您是计算机领域(AI / 大数据 / 网络 / 软件工程等)的研究者,近期有成果想投稿发表,这几个高认可度的国际会议可以参考(均支持 EI/SCOPUS 检索):

第七届计算机信息与大数据应用会议:

https://ais.cn/u/QnaAJz

第九届先进算法与控制工程会议:

https://ais.cn/u/BFfyuq

结语:

最后的最后,感谢大家观看到最后,如果对你有帮助,请**一键三连,点赞,关注(拜托了),收藏,**你的支持就是我最大的鼓励,除夕夜,小北在这里祝大家新年快乐,平安顺遂!

相关推荐
敖正炀几秒前
ArrayBlockingQueue深度解析
java
敖正炀几秒前
LinkedBlockingQueue详解
java
Lucifer三思而后行2 分钟前
中国移动 BCLinux 8.8 一键安装 Oracle 26ai
数据库·oracle
敖正炀2 分钟前
SynchronousQueue 详解
java
wuyikeer4 分钟前
Spring Boot 经典九设计模式全览
java·spring boot·设计模式
djjdjdjdjjdj4 分钟前
bootstrap如何修改警告框(Alert)的边框粗细
jvm·数据库·python
HalvmånEver7 分钟前
MySQL的数据类型(一)
数据库·mysql
努力努力再努力wz13 分钟前
【Linux网络系列】深入理解 I/O 多路复用:从 select 痛点到 poll 高并发服务器落地,基于 Poll、智能指针与非阻塞 I/O与线程池手写一个高性能 HTTP 服务器!(附源码)
java·linux·运维·服务器·c语言·c++·python
努力努力再努力wz16 分钟前
【Linux网络系列】万字硬核解析网络层核心:IP协议到IP 分片重组、NAT技术及 RIP/OSPF 动态路由全景
java·linux·运维·服务器·数据结构·c++·python
tjc1990100517 分钟前
golang如何使用t.Cleanup清理测试_golang t.Cleanup测试清理使用策略
jvm·数据库·python