【Java Maven框架】
前言
为什么要学习 Maven ?
没有 Maven 之前的开发环境:
<1> 会出现大量的重复文件,大量的 jar 包,导致项目体积增大,团队协作效率降低。
<2> 环境、版本冲突问题,比如 Windows 环境下能跑的项目在 Linux 就跑不起来。
<3> idea 虽然是自动保存自动编译的。但是打包、部署等操作还是需要我们自己完成的。自从引入 Maven 后,以上的问题差不多就已经解决了,那么 Maven 的作用是什么呢?
项目构建:提供标准的、跨平台的自动化项目构建方式 依赖管理:方便快捷的管理项目依赖的资源(iar包),避免资源间的版本冲突问题 统一开发结构:提供标准的、统一的项目结构
前期回顾:【Java 定时任务】
【Java 线程池】
【Java 阻塞队列】
【Java IO】 微信定时消息发送
目录
前言
Maven 目录结构
Maven 的骨架
pom.xml 文件
pom.xml 的三个组成部分
Maven 工程构建
Maven 工程测试
Maven 仓库
依赖与依赖程度
依赖程度
隐藏的 scope 标签
依赖版本统一提取和维护
依赖传递和依赖冲突
依赖传递
依赖冲突
父工程与子工程
maven 工程的继承
父工程统一依赖管理
maven 工程的聚合
Maven 目录结构
Maven 的骨架
我们使用 idea 创建的 Maven项目,它有固定的骨架结构如下:
maven-project
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ ├── java
│ └── resources
└── target
maven-project 是项目名称,其他的骨架的作用如下
骨架名称 | 作用 |
---|---|
pom.xml | 项目描述文件 |
src/main/java | 存放Java源码的目录 |
src/main/resources | 存放资源文件的目录 |
src/test/resources | 存放测试资源的目录 |
target | 所有编译、打包生成的文件都放在这里 |
pom.xml 文件
建立 Maven 项目的初始 pom.xml 文件:
<?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.thz</groupId>
<artifactId>maven-project</artifactId>
<version>1.0-SNAPSHOT</version>
...
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
pom.xml 的三个组成部分
pom.xml 声明
<?xml version="1.0" encoding="UTF-8"?>
pom.xml 文档类型定义
<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">
pom.xml 正文
除了以上两个部分外剩下的都属于正文部分
坐标三要素:
groupId:组ID,一般使用公司名倒置 |
artifactID:项目ID,一般就是项目名称 |
version:版本号 |
Maven 坐标的作用
使用唯一标识,唯一性定位资源位置,通过该标识可以将资源的识别与下载工作交由机器完成 。
项目的属性设置:
maven.compiler.source:源代码的 JDK 版本 |
maven.compiler.target:测试代码的 JDK 版本 |
project.build.sourceEncoding:源代码的编码方式 |
Maven 工程构建
项目构建是指将源代码、依赖库和资源文件等转换成可执行或可部署的应用程序的过程,在这个过程中包括编译源代码、链接依赖库、打包和部署等多个步骤。
我们可以在 idea 中找到 Maven 的 Lifecycle,这是 Maven 命令的可视化按钮:
Maven 命令的作用
命令 | 描述 |
---|---|
compile | 编译项目,生成 target 文件 |
package | 打包项目,生成 jar 文件 |
clean | 清理编译或打包后的项目结构 |
install | 打包后上传到 maven 本地仓库 |
deploy | 只打包,上传到 maven 私服 |
site | 生成站点(报告) |
test | 执行测试源(测试) |
Maven 工程测试
我们可以写一些代码来测试一下:
Demo:
public class Demo {
public String Func(String name){
System.out.println("Hello "+name);
return "Hello "+name;
}
}
DemoTest:
public class DemoTest {
@Test
public void Test(){
Demo demo = new Demo();
String ret = demo.Func("Maven");
Assert.assertEquals(ret, "Hello Maven");
}
}
pom.xml:
导入以下测试需要的依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
此时我们只需点击 Maven 自带的按钮程序即可运行:
test:
此时我们可以看见所有运行编译的文件都放在 target 中
package:
此时我们就可以看到打成 jar 的文件
但是注意此时这个 jar 包实际只有 3KB,它打包的只是项目的源文件,但是我们的依赖并没有被打包进 jar 包
通过执行一些指令,我们发现执行后面的指令时,会把前面的指令都执行一边。比如执行编译指令,首先会执行清理将之前的 target 文件给清理掉。
插入仓库的概念:
Maven 仓库
仓库的概念:用于存储资源,包含各种 jar 包
仓库分类 作用 远程仓库 非本机电脑上的仓库,为本地仓库提供资源 本地仓库 自己电脑上存储资源的仓库,连接远程仓库获取资源 中央仓库 Maven 团队维护,存储所有资源的仓库 私服 部门、公司范围内存储资源的仓库,从中央仓库获取资源
私服的作用:
保存具有版权的资源,包含购买或自主研发的jar 中央仓库中的iar都是开源的,不能存储具有版权的资源 一定范围内共享资源,仅对内部开放,不对外共享
举个例子,当导入一个依赖出现爆红的情况,说明我们本地仓库中没有这个依赖的 jar 包;当我们刷新 Maven 会发现右下角有一个进度条,这说明从 Maven 中央仓库下载依赖。当然你也可以自己从官网中下载需要依赖包:点击进入 Maven 中央仓库
注意 install 是比较常用的一个指令,当你写完项目代码都应该点一下,这样团队成员访问你的代码都是最新的。例如,你的项目源代码改了,但是你的本地仓库的代码没有改,这个时候团队的其他成员就访问不到你改好后的代码。
工程构建的好处:
项目构建是软件开发过程中至关重要的一部分,它能够大大提高软件开发效率,使得开发人员能够更加专注于应用程序的开发和维护,而不必关心应用程序的构建细节。
同时,项目构建还能够将多个开发人员的代码汇合到一起,并能够自动化项目的构建和部署,大大降低了项目的出错风险和提高开发效率。
依赖与依赖程度
依赖程度
隐藏的 scope 标签
我们之前添加依赖,都是直接添加坐标三要素即可,但是它还有一个隐藏的标签 - scope,决定这个依赖的依赖程度。
<dependencies>
<dependency>
<groupId>项目组织</groupId>
<artifactId>项目名称</artifactId>
<version>项目版本</version>
<scope>依赖的程度</scope>
</dependency>
</dependencies>
scope 常用的取值有四个:
取值 | 作用 |
---|---|
compile | 如果不设置 scope,默认值就是 compile 表示源代码环境需要,测试环境也需要,并且打包的时候包含 |
provided | 源代码环境需要,测试环境也需要,但是打包的时候不包含 |
test | 源代码环境不需要,打包的时候不包含,测试环境需要 |
runtime | 运行时需要,编译时不需要(源代码环境不需要,测试环境不需要),并且打包的时候包含 |
compile 就不在过多赘述了,比较好理解。我们来看看其他取值的含义:
provided (源代码环境需要,测试环境也需要,但是打包的时候不包含)
servlet-api 源代码与测试环境都需要,但是我们的 Tomcat 中已经存在,打包的时候就可以不需要了。
test (源代码环境不需要,打包的时候不包含,测试环境需要)
这里举个例子,比如,你的项目都已经测试好了,项目准备部署,我还需要测试代码做什么呢?测试只是检验源代码的正确性,而不涉及项目的部署,所以打包的时候不需要。
runtime (运行时需要,编译时不需要,并且打包的时候包含)
这里举个例子,比如,你可能在编译的时候只需要 API、JAR,而只有在运行的时候才需要 JDBC 驱动实现。
依赖版本统一提取和维护
先来说一下之前写法的弊端:
...
<groupId>com.thz</groupId>
<artifactId>maven-project</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
...
</dependencies>
...
例如:项目的依赖一多,假设某天需要修改一个依赖的版本号,我是不是还需要去那么多依赖中去找,这就显得很麻烦。有没有更直接的方法呢?可以将 version 这行提取出来,统一维护。
...
<groupId>com.thz</groupId>
<artifactId>maven-project01</artifactId>
<version>${project01.version}</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
...
</dependencies>
<!--声明版本-->
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--命名随便,内部制定版本号即可-->
<project01.version>1.0</project01.version>
<junit.version>4.12</junit.version>
<mysql.version>8.0.33</mysql.version>
...
</properties>
例如:<project01.version>1.0</project01.version> 相当与取别名,project01.version 对应的就是 1.0 这个版本号。而 ${project01.version} 的 ${} 操作就是提取这个别名的内容。
我们将依赖的版本号统一放在 properties 中维护,这样就可以很明确的知道什么依赖用了什么版本;当需要修改依赖的版本号时就可以在此修改,相对应的 ${...} 的值也会修改。
依赖传递和依赖冲突
依赖传递
像以上这种情况,B依赖于A,C依赖于B;那么C是否依赖于A呢?答案是肯定的。之前我们提到有个隐藏标签 scope ,没有明确的写,那么就是默认值 compile。三个环境都需要依赖A,那么C在打包的时候还是需要将A一起打包的。所以说,C依赖于A。
将 B 的 scope 标签换成 provided (源代码环境需要,测试环境也需要,但是打包的时候不包含)。
这个时候C是否依赖于A呢?因为B是provided标签修饰的,打包时不包含,也就是说C是看不见 A 的。所以此时 C 并不依赖于 A。
依赖冲突
举个例子:
项目B依赖于mysql5.7,项目A依赖于mysql8.0;此时C同时依赖于A、B。那么项目 A、B 必然会因为 mysql 的版本号冲突。那么怎么解决冲突呢?其实很简单就是排除掉一个。
<!--使用exclusions标签配置依赖的排除-->
<exclusions>
<!--在exclusion标签中配置一个具体的排除-->
<exclusion>
<!--指定要排除的依赖的坐标(不需要写version)-->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</exclusion>
</exclusions>
为了更好的展示,这里新建了两个子模块
untitled1:
...
<artifactId>untitled1</artifactId>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
...
untitled2:
...
<artifactId>untitled2</artifactId>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
</dependencies>
...
untitled:
...
<modules>
<module>untitled1</module>
<module>untitled2</module>
</modules>
<dependencies>
<dependency>
<groupId>com.thz</groupId>
<artifactId>untitled2</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.thz</groupId>
<artifactId>untitled1</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
...
此时,我在 untitled 中导入了 untitled1 和 untitled2 依赖;但是会因为 untitled1 和 untitled2 中的数据库版本问题而冲突。
可以观察到 idea 帮我们检查到了冲突并且还做了版本屏蔽;但是 idea 不是万能的有些冲突问题还需要自己手动解决。以上屏蔽了 mysql8.0 的版本,那么我想要屏蔽 mysql5.0 的版本该怎么办呢?- 可以使用 exclusion 排除标签。
...
<dependencies>
<dependency>
<groupId>com.thz</groupId>
<artifactId>untitled2</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
...
此时使用的就是 mysql8.0 的版本。依赖冲突问题最好在导入依赖的时候先想会发生什么冲突,如果等到发生了冲突再来排查,这样排查的时间成本会很高,因为一个项目导入的依赖是非常多的。
父工程与子工程
maven 工程的继承
利用 Maven 可以对项目进行分模块开发。那么怎样把各个模块整合到一起呢?
这就利用了 Maven 继承的特性。一般是每个模块都继承一个父工程。
子类:声明父项目的地址
<!--定位父项目位置-->
<parent>
<groupId>com.thz</groupId>
<artifactId>maven-project01</artifactId>
<version>1.0</version>
</parent>
<!--子项目的名称-->
<artifactId>untitled1</artifactId>
我们在子工程中通过 parent 标签定位父工程的位置,这样的好处就是可以把一些共性放在父类里面。然后我们有多个子工程,这些子工程就没有必要去设置这个共性。因为它默认情况会继承。
父工程统一依赖管理
父项目声明依赖:
- 使用 dependencyManagement 标签配置对依赖的管理,被管理的依赖并没有真正被引入到工程,只有在子项目调用了才会被引入工程。
<!-- 使用 dependencyManagement 标签配置对依赖的管理 -->
<!-- 被管理的依赖并没有真正被引入到工程 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>6.0.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>6.0.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>6.0.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.10</version>
</dependency>
</dependencies>
</dependencyManagement>
子项目继承依赖:
- 子工程引用父工程中的依赖信息时,可以把版本号去掉,因为使用的是父工程默认的版本号。
<!-- 子工程引用父工程中的依赖信息时,可以把版本号去掉。 -->
<!-- 把版本号去掉就表示子工程中这个依赖的版本由父工程决定。 -->
<!-- 具体来说是由父工程的 dependencyManagement 来决定。 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
</dependencies>
maven 工程的聚合
父类:声明子项目的模块
<modules>
<module>untitled1</module>
<module>untitled2</module>
</modules>
Maven 项目中的<modules>元素,它指定了当前项目的子模块列表。在这个例子中,项目有两个子模块,分别是untitled1和untitled2。这意味着这个项目是一个聚合项目,它管理着多个子模块的构建和依赖关系。当你运行 Maven 命令时,Maven会自动构建所有的子模块。