当前位置:Java -> Maven故事
回归到 Maven 的新博客 - 并没有传奇故事,只是对我旧项目的简单更新。就像与一个老朋友见面一样,分享一些新鲜事。在这篇博客中,我们将深入了解 Maven 的世界,探索我对一个旧项目所做的调整和改进。无论你是 Maven 老手还是刚开始接触,让我们一起共同探索我的 Maven 之旅吧!
在一个技术探险中,我被要求升级用于 Glassfish 或 WebLogic 等服务器上的产品的应用服务器。目标是提升安全性并为未来的 JDK 升级做准备。该产品包括 20 多个 EJB 项目和 20 多个 war 项目被打包成一个 EAR。
这些 EJB(*-ejb.jar) 和 WEB (*.war 项目) 是基于 Ant 的 Java 项目。
在这个 Ant 项目的世界中,出现了一个大问题——确定和升级应用服务器相关的依赖关系。目前的设置使用了两个项目来持有各种依赖项,这使得难以知道哪个项目使用了什么。在服务器升级期间进行更新变成了一个手动且容易出错的任务。
为了解决这些问题,我旨在简化未来的升级并集中依赖关系控制。虽然 Ant IVY 看起来很有帮助,但我担心管理依赖项和混乱的项目结构。Maven 和 Gradle 都是选择,但因为 Maven 拥有强大的工具和插件供应用服务器使用,Maven 占据上风。
借鉴开源项目和 Spring 项目的结构,Maven 的多模块与 Bill Of Materials (BOM) 成为一个引人注目的选择。
为了更容易理解,可以将下面的图片视为我们组织项目的旧方式。
Ant 中的 Module_A 项目被编译成四个二进制文件。然而,这与 Maven 要求每个 Ant 项目都要维护一个 pom.xml 的政策相矛盾。
在这里出现了一个关键问题:我能够将 Maven 无缝集成到现有的 Ant 项目结构中吗?
答案在于创建一个父项目,其 POM 作为 BOM。这种方法提供了一个精炼的项目结构,符合 Maven 的目录布局标准。
最终的设置变成了一个 Maven 多模块项目,/Application/pom.xml 控制着所有模块的版本。application_ear 项目将 EJB 和 WAR 文件打包成可部署的 EAR,而 BuildModules 则创建了一个可执行的应用服务器 zip 包。
在这种情况下,/Module_A 本身是一个 Maven 模块,其父项目为 /Application。此外,在旧项目结构中为 Module_A 创建的每个二进制文件,我建立了一个以上级为父项目的 Maven 模块。这种设计反映了我们项目的理想 Maven 多模块项目结构。
我将在本博客的后面部分深入探讨这种 *_interface 打包背后有趣的原因。
如前所述,我建立了一个名为 /Application 的主要 Maven 项目,将所有子域项目列在其下方。
关于版本控制,/Application/pom.xml 充当一个 BOM,覆盖了中心化的依赖和插件管理。下面的 XML 片段展示了 log4j-1.2-api 和 Gson 的版本声明。
<properties>
<version.log4j.1.2.api>2.19.0</version.log4j.1.2.api>
<version.gson>2.8.6</version.gson>
</properties>
<dependencyManagement>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<version>${version.log4j.1.2.api}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${version.gson}</version>
</dependency>
</dependencyManagement>
因此,所有继承或使用这些依赖项的子模块都无需指定版本信息,正如后续的 XML 片段所示:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<scope>compile</scope>
</dependency>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${version.maven.jar.plugin}</version>
<configuration>
<archive>
<index>false</index>
<manifestEntries>
<Built-By>${user.name}</Built-By>
<Build-Date>${maven.build.timestamp}</Build-Date>
<Build-Jdk>${java.vendor}</Build-Jdk>
</manifestEntries>
<manifest>
<addClasspath>false</addClasspath>
</manifest>
</archive>
</configuration>
</plugin>
处理内部或内部依赖,我们在内部网络中建立了一个类似于 JFrog Artifactory 的内部 Maven 仓库。 为我们的产品依赖项制定了一个专门的仓库,并且已经集成了这个仓库以进行有效的依赖解析。
<repositories>
<repository>
<name>Internal Dev</name>
<id>maven-dev</id>
<url>${maven.dev.repo.url}</url>
<layout>default</layout>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
为了确保在编译期间的 Java 发行和版本的一致性,我们遇到了在 Ant 构建模型中使用不同 JDK版本的意外编译。 解决了这个问题,我使用了maven enforcer plugin,这个插件允许我们对 JDK 版本和发行版本 强制执行规则,就像下面的示例中说明的那样:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>${version.maven.enforcer.plugin}</version>
<executions>
<execution>
<id>enforce-java</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireJavaVendor>
<excludes>
<exclude>${oracle.jdk}</exclude>
</excludes>
<includes>
<include>${eclipse.temurin.jdk}</include>
</includes>
</requireJavaVendor>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
面临的挑战
在我们顺利迁移过程中,我们遇到了一些障碍:
1. 创建 160+ 个项目: 超过 40 个主模块,总计 160 多个项目和子模块,用默认配置和文件在所有项目中复制项目结构都非常耗时。 为了简化这个流程,我使用了 Maven 项目模板工具 Archetype。 这个工具可以创建带有资源文件和子模块默认类的项目模板结构。
2. 循环依赖: 由于旧项目基于 Ant,并且缺少严格的组件依赖模型,因此在多个领域的项目编译过程中出现了循环依赖。 Maven 不允许循环依赖。 为了解决这个问题,我在子模块下引入了 *-ejb、*-web 和 *-interfaces 项目,按关注点对 Java 类进行了分离。 INTERFACE 项目作为救世主,通过仅持有 DTO 和 EJB 接口而没有项目间依赖来解决循环依赖。 其他模块的 EJB/WEB 项目现在将自己模块或其他模块的 *-interfaces 作为依赖项。
3. 分发打包: 创建一个将 *.ear 与应用服务器捆绑成一个 zip 文件的产品分发是必要的。 由于完整的产品构建脚本基于 Ant,因此我决定使用 maven assembly plugin进行打包。 最初,我考虑将组件脚本放在父项目 /Application 中,假设构建父项目将覆盖所有子模块。 然而,这种方法导致了所有子模块上继承的组件行为,导致了有关分发依赖和配置的混乱。 尽管尝试将继承限制在子模块中,但这种解决方案被证明是无效的。
在参考 assembly plugin guide后,我创建了一个名为 BuildModules 的分发项目。 该项目能够在 EAR 和应用服务器之外生成 zip 文件,并具有对 EAR 模块的依赖,而 EAR 模块又具有对子模块二进制文件的传递依赖(*-ejb.jar 和 *.war)。
压轴之作
尽管超出了预期的时间表,项目的最终结果是值得称赞的。 随后对应用服务器进行的次要升级非常容易地完成,仅通过简单的 Maven 属性更改。
目前,OWASPdependency-check-maven 对已知发行漏洞的依赖进行了更有效的扫描。 将项目的依赖项升级以与兼容的 JDK 版本或发行版本对齐,证明是一个简单而不复杂的过程。
最终,这是一个完美的成功故事。 展望未来,我们将深入研究 Maven 改进,并在下一阶段为产品开发定制插件。
感谢你加入我们的旅程。 我期待在即将到来的博客中分享更多见解。
推荐阅读: 剑指offer 24.反转链表
本文链接: Maven故事