Java - Maven
目录
1 依赖管理
签名基础学习阶段遇到什么外部依赖,都需要手动下载 jar 包到 IDEA 给模块添加依赖,在运行时 IDEA 自己使用 -classpath 选项执行依赖 jar 包路径。Maven 的出现可以自动化帮我们到依赖仓库完成指定依赖下载和 -classpath 配置。
1.1 安装 Maven
要想使用 Maven 首先得安装它,其实 IDEA 里有自带 Maven,在 IDEA 安装目录下 plugins\maven\lib\maven3,这里学习如何替换内置版本。
去官网 maven.apache.org 根据你当前的 Java 版本下载对应版本,解压存放到 D 盘符 Maven 目录中,顺带在 Maven 目录内创建 reposityory 用来存放以后下载的依赖。
D:\
└─ Maven
├─ apache-maven-3.9.8
└─ repository
解压完后需要设置环境变量,后续的所有 IDEA 中图形化背后都是在执行 D:\Maven\apache-maven-3.9.8\bin\mvn 命令,不然怕执行失败。
- 在 Path 变量中添加 D:\Maven\apache-maven-3.9.8\bin 目录
- 确认 JAVA_HOME 中有指向 JDK 的 bin 目录,并且在 Path 有引用 %JAVA_HOME% 变量
尝试运行 mvn -v 命令看是否配置成功
C:\Users\gbb>mvn -v
Apache Maven 3.9.8 (36645f6c9b5079805ea5009217e36f2cffd34256)
Maven home: D:\Maven\apache-maven-3.9.8
Java version: 21.0.2, vendor: Oracle Corporation, runtime: D:\Java\jdk-21.0.2
Default locale: zh_CN, platform encoding: UTF-8
OS name: "windows 11", version: "10.0", arch: "amd64", family: "windows"
IDEA 中每个新建的 Maven 项目都用的是它自带了的,如果需要使用自己安装的 Maven 和配置,可以按 Ctrl + Alt + S 打开当前 IDEA 项目的设置,找到 Build, Execution, Deployment -> Build Tools -> Maven:
- 将 Maven home path 替换成刚解压的位置 D:\Maven\apache-maven-3.9.8\conf\settings.xml。
- 将 Local repository 设置成 D:\Maven\repository,如果这个目录不存在在保存设置的时候会自动创建。这一步其实也可以省略,需要你在 Maven 配置文件 settings.xml 中找到
<localRepository>/path/to/local/repo</localRepository>
,把这个仓库路径指定一下,后面指定 Maven home path 自动会读取这个仓库路径,自动配置 Local repository。
一个普通的 Maven 项目搭建,创建时选择普通的 Java Module。其中 GroupId 是写包名,这个包名是应用的域名倒着写,比如 raingray.com 写做 com.raingray,ArtifactId 是这个 Maven 模块名,写完会自动同步到 Name 上,另外 Location 创建时也会使用这个名称。
MavenDirectoryStructure 项目创建完目录结构如下。
MavenDirectoryStructure
├─ pom.xml
└─ src
├─ main
│ ├─ java
│ │ └─ com
│ └─ resources
└─ test
└─ java
默认在 test 单元测试目录中就没有 resources,需要自己创建后对 resources 右键 Mark as 设置设置为 Tests Resources,如果需要设置的目录较多可以 F4 快速设置。
Web 目录也可以从普通 Maven 项目中新建。
先添加 Web 支持。
Deployment Descriptior 是设置 web.xml 配置文件和对应 servlet 版本,Web Resource Directories 是存放 Web 的资源目录,这两个一定要设置正确。
或者也可以去 Facets 中设置,也没问题。
点击应用后自动创建对应 web 应用目录。可以看到 web 目录有个蓝色圆点,这就是 web 目录的标识。
设置好后去 Artifacts 手动部署应用。
选择刚刚的 web 模块。
OutPut directory 是部署后对应的应用部署位置。
以上步骤有个一键操作的方式,可以到 Modules 或者 Facets 面板,找到下方警告,点击 Fix -> Create Artifact 一键创建 Artifacts。创建完后需要检查下 web.xml 和资源目录位置对不对就好。
最后编辑运行配置文件,找到 Tomcat 的面板。
选择 Artifacts 中对应的名称 artifact:MavenDirectoryStructure:Web exploded 即可。
1.2 配置依赖
看一个项目是不是 Maven,可以确认目录下有没有 pom.xml,这个配置文件全称是 Project Object Model (POM),用来配置各种依赖和打包的配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.raingray</groupId>
<artifactId>MavenDirectoryStructure</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
怎么让其 Maven 自动加载依赖包呢?需要在 pom.xml 中 <dependencys>
添加 <dependency>
标签表示引入依赖,而GroupId、ArtifactId 和 Version 标签来配置要下载的依赖信息,GroupId 是软件包的网站名通常倒写,ArtifactId 是软件包的名称,Version 是软件包的版本号,这三个标签简称为 GAV,有了这三个标签就能从仓库定位到软件包的位置。
<dependencys>
<!--要添加的依赖-->
<dependency>
<groupId>com.raingray</groupId>
<artifactId>MavenDirectoryStructure</artifactId>
<version>1.0-SNAPSHOT</version>
<dependency>
<dependencys>
那怎么知道软件中 GAV 的信息呢?其实在 Maven 有仓库汇聚网站 central.sonatype.com 和 mvnrepository.com,如果不知道配置信息可以在里面搜索包的名称,这里以 JSTL 为例找到了 API 和对应实现的 GAV。
<dependencies>
<!-- jstl-api -->
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
</dependency>
<!-- jstl-api 实现类-->
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
有了标签 Maven 从哪个仓库里下载包呢?默认情况是到官方仓库 https://repo.maven.apache.org/maven2
下载,由于距离太远速度堪忧,所以仓库是可以配置镜像的,在 Maven 安装目录\conf 的 settings.xml 中配置,里面有个 <mirrors>
标签,其子标签 <mirror>
就是配置仓库地址。
<mirrors>
<!-- mirror
| Specifies a repository mirror site to use instead of a given repository. The repository that
| this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
| for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
|
<mirror>
<id>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
-->
<mirror>
<id>maven-default-http-blocker</id>
<mirrorOf>external:http:*</mirrorOf>
<name>Pseudo repository to mirror external repositories initially using HTTP.</name>
<url>http://0.0.0.0/</url>
<blocked>true</blocked>
</mirror>
</mirrors>
这里使用阿里云 Maven 镜像作为 Maven 远程仓库,在 <mirrors>
标签内添加仓库地址。
<!-- 阿里云 Maven 镜像库 -->
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
很多同一家族的软件有很多包都使用的同一版本号,为了避免重复写,可在 properties 中定义属性,通过类似于 EL 表达式的方式使用属性的值 ${}
。
<properties>
<test.version>2.20.0</test.version>
</properties>
使用值。
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>${test.version}</version>
</dependency>
1.3 刷新依赖
光是配置完依赖还暂时无法生效,发现依赖都是红色提示依赖没有找到,需要刷新依赖将其下载到本地仓库内才能使用。钥匙依赖下载的有问题可以直接删除对应仓库内依赖文件夹重新下。
1.刷新按钮
直接点击右上角的刷新按钮,或者是用 Ctrl + Shift + O 刷新。
刷新完等待一会儿会自动把两个依赖下载到本地仓库 D:\Maven\repository 中。
- D:\Maven\repository\jakarta\servlet\jsp\jstl\jakarta.servlet.jsp.jstl-api\3.0.0\jakarta.servlet.jsp.jstl-api-3.0.0.jar
- D:\Maven\repository\org\glassfish\web\jakarta.servlet.jsp.jstl\3.0.1\jakarta.servlet.jsp.jstl-3.0.1.jar
以 jstl-api 为例,可以看到 jakarta\servlet\jsp\jstl 就是 groupId,jakarta.servlet.jsp.jstl-api 是 artifactId,3.0.0 是 version。
以后每次引用依赖就会到本地仓库取,没有才去远程仓库下载。
2.reload project
对项目中 pom.xml 右键,找到 Maven -> Reload project,一样会自动下载 jar 包。
1.4 依赖使用范围
从前面的内容看 Maven 使用很简单,只需要找到我们需要的依赖,配置 GAV 即可下载使用,无需关心我们下载的 jar 包本身是否还有依赖其他软件,Maven 会自动帮我们处理好所有依赖的关系,该下载下载,无需担心。
依赖范围:编译(源码中能不能用)、测试(单元测试能不能用)、运行(打包后有没有)
1.4.1 compile
<dependency>
所引入的依赖都有范围,通过子标签 <scope>
配置。不填 scope 标签默认是 compile。
<dependencies>
<!-- jstl-api -->
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
<scope>compile</scope>
</dependency>
<!-- jstl-api 实现类-->
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
1.4.2 test
只能在单元测试类上使用。
源码不能使用依赖,打包不会携带此依赖。
1.4.3 provided
在源码和单元测试中都能用(能够被编译和测试),只是打包的时候不放进去,那不放进去代码调用肯定报错。其实不会的,我们要使用这个 provided 范围一定是知道有其他程序提供了这个依赖,就算我们这没有,调用的时候会自动去调用现有的依赖代码。这样做的好处是避免引入重复的 jar 包。
结合上面解释只能在源码和单元测试中可以使用依赖,在打包时不携带此依赖。
1.4.4 system
和 provided 原理一致,provided 也可以在源码和单元测试中使用,使用前需要用 <systemPath>
指定本地 jar 包的绝对路径,最后打包的时候不会被放进去,所以运行的时候一样会引用别人提供的 jar 包。
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>${test.version}</version>
<scope>system</scope>
<systemPath>jar 包绝对路径</system>
</dependency>
结合上面解释只能在源码和单元测试中使用依赖,在打包时不会携带上此依赖。
1.4.5 runtime
这个 jar 包只会在实际运行的时候会使用到,比如单元测试中运行过程中会调用 jar 包的功能,或者应用上线后运行的过程中调用。要注意源码是无法使用依赖,因为这个依赖很可能是 Java 接口的实现类,只会在源码中调 Java 的接口来操作,因此在编译阶段不会用到这个 jar 包。
结合上面解释只能在单元测试中使用,并且会在打包时携带上此依赖。
1.5 依赖传递
比如 A -> B -> C,那么 A 直接依赖于 B,间接依赖于 C,那么 A 可以使用依赖 C 中的功能,但是具体能不能用要看 B 设置的依赖范围,默认情况依赖只有 <scope>
设置成 compile 才能够传递。
不想被传递还可以 <optional>
设置 true。
<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>${log4j.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
1.6 依赖冲突
假如当前项目引入了依赖 fastjson,而且其他引入的依赖模块 B,它的依赖中也有 fastjson,根据依赖传递规则,此时我们项目中到底用的是哪个 fastjson?此时就是依赖冲突。不过 Maven 会根据几个规则处理依赖冲突问题。
1.短路优先
几个依赖最终一定使用 A 中的依赖 B,而不是 C 中的依赖 B,因为距离最短。
// A 的依赖
A -> B
A -> C
// C 的依赖
C -> B
2.引用顺序优先
// A 的依赖
A -> D
A -> E
// D 的依赖
D -> B
// E 的依赖
E -> B
路径长度相同时,当 A 调用 B 时,最终一定使用从依赖 D 调,而不是 E 中的依赖 B。
3.手动排除依赖
避免引用传递,就算传递过来调用的时候我也不用。比如下面就是拒绝依赖 B 中的依赖 test 传递过来,下次调 test 会从其他依赖中获取。
<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>B</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>...</groupId>
<artifactId>test</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
1.7 依赖继承
现实中大型软件都是将功能拆分成独立的项目,此时依赖就不好管理,很可能各个项目中都在使用相同依赖,但版本不一致,这就导致了重复引用,或者版本不一致导致的 bug、vulnerability 不好排查,为了保持稳定可靠,可以建立一个父工程,在 pom.xml 写好依赖 GAV 或者其他配置信息,子工程去继承使用即可,减少了不一致的风险。
1.7.1 配置继承
要完成工程依赖配置有两个步骤。
1.配置父工程
需要在 pom.xml 把打包的方式改成 pom 标识当前是父工程。
<packaging>pom</packaging>
其次填写子工程的项目文件夹路径
<modules>
<module>../MavenSon</module>
</modules>
2.配置子工程
Maven 子工程使用 <parent>
标签,在里面填父工程 GAV,继承父工程 pom.xml 配置。
<parent>
<groupId>com.raingray</groupId>
<artifactId>MavenParent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
1.7.2 使用继承的依赖
1.全部继承依赖
父工程定义的依赖,只要能够传递过来,在子工程里如需再次引入可以直接使用。
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
</dependencies>
2.继承指定依赖
实际中另一种方式能灵活,是父工程先用 <dependencies>
把依赖定死,子工程按需主动去引入,就不像 <dependencies>
把依赖全部传递过来。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
</dependencies>
</dependencyManagement>
子工程引入还是使用 <dependencies>
,只是这个 Version 不用写了,会自动继承。
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
3.使用继承的属性
父项目自定义属性,子项目也自动继承可以使用。
<properties>
<junit.version>4.13.1</junit.version>
</properties>
使用的时候语法类似 JSP 中的 EL 表达式,自动把值替换。
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
2 项目构建
从编译、测试到打包整个流程。
2.1 生命周期
Maven 构建项目有个生命周期。
- clean
- validate
- compile
- test
- package
- verify
- install
- site
- deploy
所有 Maven 命令必须进到 pom.xml 所在文件夹执行。
mvn compile
,编译打包,把项目编译成 class 字节码,这些编译后的内容都放到 target 目录中。mvn clean
,删除 target 目录,等于删除编译结果mvn test
,会自动执行 mvn test-compile 编译单元测试目录 test 中的类,并自动执行字节码,自动将测试结果放到 target 中。其中的测试类一般以 Test 结尾,其中测试方法以 test 打头。mvn validate
mvn package
,自动执行编译、测试、打包这些命令,具体打成什么包需要在 pom.xml 中配置<packaging>
mvn verify
mvn install
,自动编译打包,把最终的 jar 包放到到本地仓库mvn site
mvn deploy
执行多个命令可以给多个参数,一般是 mvn compile test
IDEA 中可以在侧边栏找到 Maven 找到对应项目中 Lifecycle 双击对应功能一样执行命令
mvn dependency:tree
命令查看当前 pom.xml 依赖树。当然 Maven 侧边栏 Dependencies 标签也能查看。
PS D:\raingray\Learn\JavaWebProject\MavenDirectoryStructure> mvn dependency:tree
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.raingray:MavenDirectoryStructure >----------------
[INFO] Building MavenDirectoryStructure 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- dependency:3.7.0:tree (default-cli) @ MavenDirectoryStructure ---
[INFO] com.raingray:MavenDirectoryStructure:jar:1.0-SNAPSHOT
[INFO] +- jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:jar:3.0.0:compile
[INFO] | +- jakarta.servlet:jakarta.servlet-api:jar:6.0.0:compile
[INFO] | \- jakarta.el:jakarta.el-api:jar:5.0.0:compile
[INFO] \- org.glassfish.web:jakarta.servlet.jsp.jstl:jar:3.0.1:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.159 s
[INFO] Finished at: 2024-06-24T19:28:47+08:00
[INFO] ------------------------------------------------------------------------
PS D:\raingray\Learn\JavaWebProject\MavenDirectoryStructure>
打包还可以选择打包成什么类型,通过 <packaging>
标签控制,不写标签默认值就是 jar 包,如果是 Web 工程也可以写成 war,写 pom 表示当前是父工程,不打包。
<packaging>war</packaging>
<build>
中添加 <filename>
可以自定义打包后的文件名称,但是不会带版本号。
<build>
<filename>指定打包后的文件名</filename>
</build>
编译打包,选择 Java 版本和字符集,保持创建是的默认值就行。
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
打包默认只会把 resources 文件夹的,而代码文件夹中的则不会,通过配置可以解决此问题。至于说为什么使用两个星号,是官方文档的 includes 描述就是这样写的。
<build>
<resources>
<resource>
<!-- 要打包的目录 -->
<directory>src/main/java</directory>
<!-- 打包所有 .xml 文件 -->
<includes>**/*.xml</includes>
</resource>
</resources>
</build>
2.2 聚合统一构建
如果项目有父子工程,也可以通过构建父工程,子工程也会自动执行相同操作。
但是聚合有一个好处是可以让很多普通的 Maven 项目执行构建。操作也简单,先建立一个父工程,最后用 <modules>
标签引入这些项目。
<!--修改打包方式为父工程-->
<packaging>pom</packaging>
<!--使用 modules 标签引入项目-->
<modules>
<!--值是项目文件夹的相对路径-->
<module>../MavenDirectoryStructure</module>
<module>...</module>
...
</modules>
2.3 插件🔨
最近更新:
发布时间: