红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 开发工具
  3. 正文

Maven 简易指南 II

2023年8月31日 909点热度 0人点赞 0条评论

1.分模块开发

1.1.意义

分模块开发的意义可以观看这个视频。

1.2.快速开始

这里作为示例的项目是maven-demo.zip,对应的示例数据是mybatis.sql。

解压后用 Idea 打开/mave-demo/mvc-demo目录的 Maven 项目。

可以尝试运行项目,确保项目是正常的。

新建一个子模块 mvc-demo-entity,用于拆分 mvc-demo 中的实体类部分代码:

image-20230831144027555

注意,这里不需要选择父模块,且目录设置为和 mvc-demo 平级。

将 mvc-demo 模块下的 entity 包拷贝到 mvc-demo-entity 模块。

会报错,因为缺少 lombok依赖,添加:

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.26</version>
    </dependency>
</dependencies>

删除 mvc-demo 模块下的 entity 包。

现在 mvc-demo 模块下依赖于实体类的类会报错,因为访问不到对应的实体类了。所以这里要添加对 mvc-demo-entity 模块的依赖:

<dependency>
    <groupId>cn.icexmoon</groupId>
    <artifactId>mvc-demo-entity</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

好了,项目已经拆分成了两个模块,其中 mvc-demo-entity 包含了项目中的实体类部分代码,这样做是按照代码层次进行横向切分。

但是现在尝试编译(maven compile)模块 mvc-demo 会报错:

[ERROR] Failed to execute goal on project mvc-demo: Could not resolve dependencies for project cn.icexmoon:mvc-demo:war:1.0-SNAPSHOT: Could not find artifact cn.icexmoon:mvc-demo-entity:jar:1.0-SNAPSHOT -> [Help 1]

这是因为虽然子模块 mvc-demo-entity 在 Idea 中存在,但 Maven 编译的时候只会从本地仓库查找,该模块并不存在于本地仓库中,显然也不可能在中央仓库,所以就无法找到并报错。

所以需要先通过 Maven 生命周期命令maven install将 mvc-demo-entity 模块安装到本地仓库:

[INFO] Installing D:\workspace\learn-maven\ch2\maven-demo\mvc-demo-entity\pom.xml to C:\Users\70748\.m2\repository\cn\icexmoon\mvc-demo-entity\1.0-SNAPSHOT\mvc-demo-entity-1.0-SNAPSHOT.pom

现在就可以成功编译 mvc-demo 模块了。

可以用同样的方式将持久层代码拆分成 mvc-demo-mapper 模块。

拆分后的示例代码可以从这里获取。

2.依赖传递

2.1.依赖传递

在我们上边的示例中,mvc-demo 模块依赖于 mvc-demo 和 mvc-demo-entity 以及 mvc-demo-mapper,而 mvc-demo-mapper 又依赖于 mvc-demo-entity,在 Idea 的依赖层级中,体现为: image-20230831152858448

现在从 mvc-demo 的依赖项中删除对 mvc-demo-entity 的依赖。现在 mvc-demo 依然可以使用相应的实体类,只不过对 mvc-demo-entity 模块的引用从直接依赖变成了间接依赖。

image-20230831153559986

2.2.依赖冲突

如果在同一个模块的依赖项中添加了两个仅版本号不同的坐标,就会存在依赖冲突:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

此时后声明的依赖会覆盖前面的依赖,所以这里实际生效的是 4.12 版本的 JUnit:

image-20230831154019113

如果存在冲突的依赖都是间接依赖,此时依赖层级深度较浅的依赖会生效。

1630853726532

比如上图中的依赖 E1 和 E2,假设它们存在依赖冲突,对于 A 来说,E1 的依赖层级是2,E2 的依赖层级是3,因此 E1 会生效。

如果是相同层级的依赖冲突,比如上图中的 D1 和 D2,此时的规则是在 A 的依赖项中先声明的会生效,换言之,如果在 A 的依赖项中,B 在 C 之前声明,那么 D1 就会生效。

2.3.依赖关系

可以通过 Idea 的 Maven 工具查看依赖之间的关系图:

image-20230831161738252

示例中三个模块的依赖关系图:

image-20230831161843680

3.隐藏依赖

3.1.可选依赖

可以通过可选依赖屏蔽外部对当前模块某些依赖的“可见性”。

比如当前 mvc-demo 通过 mvc-demo-mapper 依赖 mvc-demo-entity 模块:

image-20230831162852043

现在 mvc-demo-mapper 模块中我们将依赖 mvc-demo-entity 设置为可选依赖:

<dependency>
    <groupId>cn.icexmoon</groupId>
    <artifactId>mvc-demo-entity</artifactId>
    <version>1.0-SNAPSHOT</version>
    <optional>true</optional>
</dependency>

现在 mvc-demo-mapper 的依赖项中就看不到原本存在的间接依赖 mvc-demo-entity 了:

image-20230831163039161

同时,相应引用实体类的控制层代码会报错。

这同样说明了,依赖项默认的optional属性值是false,也就是说对外部模块是可见(可以依赖传递)的。

3.2.排除依赖

如果我们不需要某些因为依赖传递而生效的间接依赖,可以在当前模块中主动排除。

假设 mvc-demo-mapper 模块使用了 Log4j 日志:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.14</version>
</dependency>

这个依赖项不是可选的,所以显然会在模块 mvc-demo 中生效:

image-20230831163718629

假设我们在模块 mvc-demo 中不打算使用 Log4j 日志,而是使用 Logback 日志,就可以将其主动排除:

<dependency>
    <groupId>cn.icexmoon</groupId>
    <artifactId>mvc-demo-mappper</artifactId>
    <version>1.0-SNAPSHOT</version>
    <exclusions>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>

现在 mvc-demo 的依赖中就不再有 Log4j 的间接依赖了。

image-20230831163912424

4.聚合和继承

4.1.聚合

现在我们已经将一个项目拆分成多个模块,这些模块之前存在依赖关系。如果模块之前有改动,要重新编译整个项目,我们就要考虑这些依赖关系。比如在当前这个示例中,就需要先编译和安装 mvc-demo-entity 模块,在编译和安装 mvc-demo-mapper 模块,最后编译 mvc-demo 模块。

可以用一个聚合模块来解决这个问题。

在 Idea 中创建一个新模块 mvc-demo-parent,这个模块将管理其它的模块,所以不需要添加任何代码。

在 pom.xml 文件中将打包方式设置为pom:

<packaging>pom</packaging>

添加其它模块信息:

<modules>
    <module>../mvc-demo</module>
    <module>../mvc-demo-mappper</module>
    <module>../mvc-demo-entity</module>
</modules>

module标签中指定其它模块相对于 mvc-demo-parent 模块的路径。

现在 Maven 工具中其它模块就会被包含在 mvc-demo-parent 模块下:

image-20230831170029519

对 mvc-demo-parent 模块进行编译:

[INFO] Reactor Build Order:
[INFO] 
[INFO] mvc-demo-entity                                                    [jar]
[INFO] mvc-demo-mappper                                                   [jar]
[INFO] mvc-demo Maven Webapp                                              [war]
[INFO] mvc-demo-parent                                                    [pom]

可以看到输出信息中 Maven 会按照各模块之间的依赖关系将模块依次进行编译。

4.2.继承

除了可以在模块之间设置聚合关系,还可以设置继承关系。让一个模块继承另一个模块后,子模块可以继承父模块的 Maven 配置(主要是依赖项),这样做可以简化配置以及实现版本管理。

首先我们需要一个父模块,并且其打包方式为 pom,这里就使用之前添加的 mvc-demo-parent 模块。

通常聚合和继承会同时使用,作为聚合使用的模块也同时会作为其它子模块的父模块。

在模块 mvc-demo 中添加父模块信息:

<parent>
    <groupId>cn.icexmoon</groupId>
    <artifactId>mvc-demo-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../mvc-demo-parent/pom.xml</relativePath>
</parent>

relativePath标签指向父模块的 pom 文件,用于快速定位父模块,这是可选的。

子模块中通用的依赖可以放在父模块中,这样子模块中就无需再次添加依赖,可以直接从父模块中继承并使用。

这里假设所有的子模块都需要 Spring 相关依赖,因此将 Spring 相关的依赖添加到父模块:

<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
   	<!-- ... -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.75</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.0</version>
    </dependency>
</dependencies>

这些依赖就可以从子模块 mvc-demo 中删除,因为它们现在可以从父模块中继承。

并非所有依赖都是所有子模块都需要的,对于只有部分子模块会用到的依赖,可以在父模块中进行依赖管理:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
    </dependencies>
</dependencyManagement>

在子模块 mvc-demo-mapper 中,相应的依赖不再需要指定版本号:

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
</dependency>

因为该依赖已经被父依赖管理,所以版本号会使用父依赖指定的版本号:

image-20230831173628511

这样做的好处是可以统一管理版本,减少版本不兼容的问题出现。

5.属性

5.1.自定义属性

项目中使用了多个 Spring 相关依赖,并且这些依赖使用的版本号相同:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

这会对版本维护造成麻烦,比如某个 Spring 依赖的版本因为疏忽设置的与其它 Spring 依赖不一致。

可以使用 Maven 属性解决这个问题:

<properties>
    <spring.version>5.2.10.RELEASE</spring.version>
</properties>

properties标签用于定义属性,子标签的名称就是属性名称。

使用属性统一版本号:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>
</dependency>

可以用这种方式将所有依赖的版本都集中管理:

<properties>
    <spring.version>5.2.10.RELEASE</spring.version>
    <mysql-connector-j.version>8.0.33</mysql-connector-j.version>
    <javax.servlet-api.version>3.1.0</javax.servlet-api.version>
    <druid.version>1.1.16</druid.version>
    <mybatis-spring.version>2.1.1</mybatis-spring.version>
    <lombok.version>1.18.26</lombok.version>
    <fastjson.version>1.2.75</fastjson.version>
    <jackson-databind.version>2.9.0</jackson-databind.version>
    <mybatis.version>3.5.5</mybatis.version>
    <junit.version>4.13</junit.version>
    <aspectjweaver.version>1.9.4</aspectjweaver.version>
</properties>

Idea 中在依赖的版本号上右键,选择 Refactor -> proerty,可以自动将依赖的版本号设置为属性。

5.2.properties 加载 Maven 属性

如果需要,可以将 properties 文件中的属性用 Maven 管理和加载。

比如在 mvc-demo-parent 模块中添加自定义属性:

<properties>
    <jdbc.username>root</jdbc.username>
    <jdbc.password>mysql</jdbc.password>
    <jdbc.driver>com.mysql.cj.jdbc.Driver</jdbc.driver>
    <jdbc.url>jdbc:mysql:///mybatis?useSSL=false</jdbc.url>
</properties>

在子模块 mvc-demo 的 properties 文件中使用这些属性:

jdbc.username=${jdbc.username}
jdbc.password=${jdbc.password}
jdbc.driver=${jdbc.driver}
jdbc.url=${jdbc.url}

仅仅这样做还不够,还需要在 mvc-demo-parent 模块配置中告诉 Maven 相关插件,在处理 properties 文件所在目录时,使用 Maven 属性:

<build>
    <resources>
        <resource>
            <directory>../mvc-demo/src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

filtering标签为true表示开启 Maven 的过滤器,用于解析指定目录下的资源文件,如果其中有${...},就用 Maven 属性进行替换。

现在 Idea 的 mvc-demo-parent 模块下会出现一个 resources 选项,它下面就是被 Maven 管理的目录:

image-20230831185901190

可以对父模块 mvc-demo-parent 进行安装,并在生成的 mvc-demo 模块的 war 包中查看 properties 文件内容是否使用了 Maven 属性。

5.2.1.web.xml

实际上这里编译会出错,提示找不到 WEB-INF/web.xml 文件。我们的 mvc-demo 模块通过相关配置类设置 Servlet 配置,所以并不需要 web.xml 文件,但 Maven 生成 war 包时会强制检查是否存在这个文件,不存在就报错。

解决方式有两种,简单的方式为添加一个空的 web.xml 文件以欺骗 Maven 的相关插件。复杂方式为修改相关 Maven 插件的设置。

在 mvc-demo 的 POM 配置中添加:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.2.2</version>
            <configuration>
                <failOnMissingWebXml>false</failOnMissingWebXml>
            </configuration>
        </plugin>
    </plugins>
</build>

这里的作为插件属性的failOnMissingWebXml标签设置为false,Maven 生成 war 包时就不会再强制检查 web.xml 文件是否存在了。

现在对父模块执行安装,就能获得 mvc-demo 模块的 war 包,在该 war 包的/classes 目录下可以找到jdbc.properties 文件,可以看到该文件正常从 Maven 属性加载了值:

jdbc.username=root
jdbc.password=mysql
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql:///mybatis?useSSL=false

5.2.2.project.basedir

在现在的示例中,只有 mvc-demo 的 resources 目录下的文件可以使用 Maven 属性,因为:

<directory>../mvc-demo/src/main/resources</directory>

如果要让所有的子模块的 resources 目录都可以使用 Maven 属性,需要使用 Maven 的内置属性:

<directory>${project.basedir}/src/main/resources</directory>

内置属性project.basedir表示当前项目的根目录,当 Maven 插件编译 maven-demo 模块时,project.basedir的值就是 maven-demo 的根目录。

5.3.其它属性

除了自定义属性,Maven 中可以使用多种属性:

1630981519370

可以使用mvn help:system命令查看 Maven 可以使用的 Java 系统属性和环境变量属性:

===============================================================================
System Properties
===============================================================================

java.runtime.name=OpenJDK Runtime Environment
sun.boot.library.path=D:\software\coding\eclipse_jdk1.8\jre\bin
java.vm.version=25.332-b09
java.vm.vendor=Temurin
maven.multiModuleProjectDirectory=C:\Users\70748\AppData\Local\Microsoft\WindowsApps
java.vendor.url=https://adoptium.net/
path.separator=;
guice.disable.misplaced.annotation.check=true
java.vm.name=OpenJDK 64-Bit Server VM
...
===============================================================================
Environment Variables
===============================================================================

GATEWAY_VM_OPTIONS=D:\software\coding\IntelliJ IDEA 2021.3.2\ja-netfilter-all\vmoptions\gateway.vmoptions
DATASPELL_VM_OPTIONS=D:\software\coding\IntelliJ IDEA 2021.3.2\ja-netfilter-all\vmoptions\dataspell.vmoptions
COMSPEC=C:\WINDOWS\system32\cmd.exe
NUMBER_OF_PROCESSORS=16
SYSTEMROOT=C:\WINDOWS

可以像自定义变量那样使用,比如${java.vm.version}。

5.4.版本管理

在我们jar包的版本定义中,有两个工程版本用的比较多:

  • SNAPSHOT(快照版本)

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

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

  • RELEASE(发布版本)

    • 项目开发到一定阶段里程碑后,向团队外部发布较为稳定的版本,这种版本所对应的构件文件是稳定的

    • 即便进行功能的后续开发,也不会改变当前发布版本内容,这种版本称为发布版本

除了上面的工程版本,我们还经常能看到一些发布版本:

  • alpha版:内测版,bug多不稳定内部版本不断添加新功能

  • beta版:公测版,不稳定(比alpha稳定些),bug相对较多不断添加新功能

  • 纯数字版

The End,谢谢阅读。

本文的完整示例可以从这里获取。

参考资料

  • 黑马程序员SSM框架教程

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: maven
最后更新:2023年8月31日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

取消回复

*

code

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号