什么是Maven
Maven是一款用于管理和构建Java项目的工具,它基于项目对象模型(POM)的概念(把项目看成一个对象),通过一小段描述信息(pom.xml)来管理项目的构建。它提供了方便快捷的依赖管理(jar包)、标准化的跨平台的自动化项目构建方式、标准统一的项目结构。在以前我们想要导入jar包,要先到官网下载,然后再将其拷贝到我们的项目中,再将其移入到lib文件夹下。而对于Maven,我们只需要描述我们需要导入的jar包的信息即可。配置好信息后Maven会自动帮我们下载这个jar包。
方便快捷的依赖管理
用Maven构建Java项目后,可以看到其携带的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>org.example</groupId>
<artifactId>maven_project</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
点击右侧的m形状的按钮,可以看到三个内容:生存期、插件、仓库。我们现在导入commons-IO这个jar包:
<?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>org.example</groupId>
<artifactId>maven_project</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
</project>
点击右侧的刷新按钮,就可以看到多了一项“依赖项”,里面是commons-io,想要更换版本时只需要更改version标签的版本即可。
标准化、跨平台的自动化项目构建方式
我们的java项目想运行,要先经过java c指令进行编译(IDEA会自动帮我们编译出.class字节码文件)。如果我们开发的项目比较大,模块比较多,那这个编译->测试->打包->发布的流程也是比较繁琐的。而在Maven中这一系列操作都已经被标准化了。我们可以基于Maven中的指令快速完成这个流程。
在右侧面板中有一项叫生存期(Lifecycle),里面有一项叫做compile,双击它,它就会自动地对这个项目当中所有的源代码进行编译操作。编译完成后,可以看到项目结构中出现了target文件夹,其下的classes文件夹里放有编译完成后的.class文件。
打包同理,找到package,双击它,打包完成后可以看到target下新出现的.jar文件。这些指令在Windows\Linux\Mac上都能使用。
统一的项目结构
由于项目结构存在差异,eclipse创建的java项目是无法直接导入到IDEA中的,反之亦然。而只要基于Maven构建出的Java项目,目录结构都是统一的。模块下有一个src,用来存放源代码。src下有两个子目录main和test,main存放主程序。main下有两个子目录java和resources,java下存放的是java源代码,resources存放配置文件。test下java文件夹,存放测试相关的代码。需要的话可以在test下创建resources文件夹,存放测试相关的配置文件。
<?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>org.example</groupId>
<artifactId>maven_project</artifactId>
<version>1.0-SNAPSHOT</version>
<!--描述项目的属性-->
<properties>
<!--JDK版本与字符集-->
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--描述项目的依赖-->
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
</project>
pom.xml -> 项目对象模型(POM) <-> 依赖管理模型 -> 本地仓库 <-> 中央仓库(或私服 <-> 中央仓库)
仓库:用于存储资源,管理各种jar包
本地仓库:自己计算机上的一个目录
中央仓库:由Maven团队维护的全球统一的仓库。仓库地址:https://repol.maven.org/maven2/
远程仓库(私服):一般由公司团队搭建的远程仓库
Maven的安装
1.安装并解压apache-maven-3.9.12-bin.zip
官网:Download Apache Maven – Maven
2.配置本地仓库,新建mvn_repo文件夹,修改conf/settings.xml中的<localRepository>(第53行,把它复制一份移动到注释区外面)为一个指定目录。也就是说现在这个mvn_repo就是maven本地仓库的目录。
<localRepository>C:\maven\apache-maven-3.9.4\mvn_repo</localRepository>
3.配置阿里云私服:如果不配置私服,那么默认会连接中央仓库,从中央仓库下载依赖比较慢。修改conf/settings.xml中的<mirrors>标签(第149行,先把里面已经存在的一个mirror标签删掉),为其添加如下子标签
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<mirrorOf>central</mirrorOf>
</mirror>
4.配置环境变量:MAVEN_HOME 为maven的解压目录,并将其bin目录加入PATH环境变量。配置好环境变量后就可以在任意目录下执行maven指令了。
此电脑 -> 右键找到属性 -> 高级系统设置 -> 环境变量 -> 系统变量。设置变量名为MAVEN_HOME,值为maven的安装目录。再找到Path,在里面增加一条%MAVEN_HOME%\bin,%%表示引用,这表示我们也要将maven安装目录下的bin目录也加入到环境变量中。
5.检查:在cmd中输入mvn -v,查看maven版本号。
IDEA集成Maven
创建Maven项目
配置Maven环境(全局)
文件 -> 关闭项目 -> 自定义 -> 所有设置 -> 构建、执行、部署 -> 构建工具 -> 点击maven -> 配置Maven主路径和用户设置文件。如图:

接着,在maven目录下找到运行程序,配置一下JRE版本。
再在构建、执行、部署目录下找到编译器 -> Java编译器,配置Java编译器的版本。
回到IDEA,新建空项目 -> 文件 -> 设置,同样找到maven,可以看到我们的全局配置确实生效了。再来配置一下项目的JDK版本。找到项目结构,选择SDK与对应的语言级别即可。
那么这个空项目的maven环境和JDK我们就配置好了。接着我们创建Maven项目。
创建模块 -> 填写模块信息 -> 选择语言为Java,构建工具为Maven -> 点击create -> 创建完成
可以看到整体的项目结构和我们上文所说的是一样的。
我们现在再src的main目录下的java文件夹里创建一个HelloWorld.java:
package com.tkevinjd;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello Maven!");
}
}
运行后可以看到多了一个与src平齐的target目录,它里面存放的是编译后的字节码文件以及打包后的jar包文件。
Maven坐标
在pom.xml中:
<groupId>com.tkevinjd</groupId>
<artifactId>maven-project01</artifactId>
<version>1.0-SNAPSHOT</version>
这一段项目配置,我们称之为Maven坐标。所谓坐标,就是资源(jar)中的唯一标识,通过该坐标可以唯一定位资源位置。使用坐标来定义项目或引入项目中需要的依赖。
Maven坐标主要组成:
groupId:定义当前Maven项目隶属组织名称(通常是公司域名反写)
artifactID:定义当前Maven项目名称(通常是模块名称,例如order-service\goods-service)
version:定义当前项目版本号。如果版本号后面有SNAPSHOT标识,表示这是功能不稳定、处于开发中的版本,即快照版本。RELEASE标识表示这是功能稳定、当前更新停止、可以用于发行的版本。
导入Maven项目
方式一:文件 -> 项目结构 -> 模块 -> 导入模块 -> 选择maven项目中的pom.xml
方式二:Maven面板(右侧的m) -> 找到加号(Add Maven Projects) -> 选择maven项目中的pom.xml
成功导入后,项目名称会加粗,pom.xml文件前面的logo也会变成一个大的蓝色小写m
依赖配置
配置:
1.在pom.xml中编写<dependencies>标签
2.在<dependencies>标签中使用<dependency>引入坐标
3.定义坐标的groupId\artifactId\version
4.点击刷新按钮,引入最新加入的坐标
如果不知道依赖的坐标信息,可以到https://mvnrepository.com/中搜索
比如输入spring-context,找到6.1.4版本,就可以看到它的坐标
刷新之后,可以在右侧的maven面板的项目依赖里看到新加入的依赖。另外,我们可以看到除了我们自己加入的依赖,还有一些我们没有自己加入的依赖。这是maven的依赖传递,即我们配置了依赖A,而A依赖B,B依赖C,那么就会把B和C这两个依赖都加入进来。
如果我们不需要某个依赖,也可以排除它,使用<exclusions>标签即可,无需指定版本
<exclusions>
<exclusion>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</exclusion>
</exclusions>
生命周期
Maven的生命周期就是为了对所有的maven项目构建过程进行抽象和统一。Maven中有3套相互独立的生命周期:
clean:清理工作
default:核心工作,如编译、测试、打包、安装、部署等
site:生成报告、发布站点等
每一套生命周期又会分为一些具体的阶段。这些具体的阶段是有顺序的,后面阶段的执行是需要依赖于前面阶段的。重点关注五个阶段:
clean:移除上一次构建生成的文件
compile:编译项目源代码
test:使用合适的单元测试框架运行测试(junit)
package:将编译后的文件打包,如:jar\war等
install:安装项目到本地仓库
同一套生命周期中,当运行后面的阶段时,前面的阶段都会运行。也就是说如果我们运行了install,那么compile肯定会运行,但clean不会,因为compile和install都属于default这一套生命周期,而clean属于clean这一套生命周期。
执行指定生命周期的两种方式:
1.在IDEA中通过右侧的maven工具栏,选中对应的生命周期,双击执行
2.在命令行中,通过命令执行
比如执行clean操作。在maven工具栏中双击clean,可以看到target文件夹被删除了。日志里也有这么一段话:
[INFO] Deleting C:\Users\31604\Desktop\Personal\JavaWeb\code\web-ai-project01\maven-project01\target
如果执行install操作,那么就是把当前项目安装到本地仓库。通过日志我们可以看到它进行了编译与打包(如果有单元测试就会在编译之后进行测试)。这也印证了同一套生命周期中,运行后面的阶段时,前面的阶段都会运行。
想要在本地仓库mvn_repo中找到这个项目,可以顺着这个项目的坐标找到对应的jar包。
也可以使用命令行进行操作。我们在maven项目的目录下打开命令行窗口。如果要执行clean,输入:
mvn clean
其它都是类似的。
实际上这些clean\compile等操作在底层都是通过插件完成的,可以说maven相当于是一个插件执行框架。在maven面板的右侧的plugin栏我们也可以看到这些操作对应的插件。
测试
根据测试的阶段,可以分为单元测试、集成测试、系统测试、验收测试
单元测试是对软件的基本组成单位进行测试。集成测试是将已分别通过测试的单元,按设计要求组合成系统或子系统,再进行的测试,目的是检查单元之间的协作是否正确。
测试方法:白盒测试、黑盒测试、灰盒测试
白盒:清楚软件内部结构、代码逻辑。用于验证代码、逻辑正确性。
黑盒:不清楚软件内部结构、代码逻辑。用于验证软件的功能、兼容性等。
灰盒:既关注软件的内部结构,又考虑外部表现。
单元测试为白盒,集成为灰盒,后面的为黑盒。
单元测试
JUnit:最流行的Java测试框架之一,提供了一些功能,方便程序进行单元测试。
为什么不直接在main里面调用方法来进行测试呢?原因有三个:
1.测试代码与源代码未分开,难以维护
2.一个方法测试失败,影响后面方法
3.无法自动化测试,得到测试报告
使用JUnit可以规避上述问题
案例:使用JUnit对UserService中业务方法进行单元测试。UserService.java:
package com.tkevinjd;
import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;
public class UserService {
//给定一个身份证号,计算出该用户的年龄
public Integer getAge(String idCard) {
if(idCard == null || idCard.length() != 18) {
throw new IllegalArgumentException("无效的身份证号");
}
String birthDate = idCard.substring(6, 14);
LocalDate parse = LocalDate.parse(birthDate, DateTimeFormatter.ofPattern("yyyyMMdd"));
LocalDate now = LocalDate.now();
Period period = Period.between(parse, now);
return period.getYears();
}
//给定一个身份证号,计算出该用户的性别
public String getGender(String idCard) {
if(idCard == null || idCard.length() != 18) {
throw new IllegalArgumentException("无效的身份证号");
}
String genderCode = idCard.substring(16, 17);
int genderInt = Integer.parseInt(genderCode);
if(genderInt % 2 == 0) {
return "女";
} else {
return "男";
}
}
}
1.在pom.xml中引入JUnit的依赖
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.1</version>
</dependency>
2.在test/java目录下,创建测试类,编写对应的测试方法,并在方法上声明@Test注解。
JUnit单元测试类名 命名规范为:XxxxTest,JUnit单元测试的方法,必须声明为public void
UserServiceTest.java:
package com.tkevinjd;
import org.junit.jupiter.api.Test;
public class UserServiceTest {
@Test
public void testGetAge() {
Integer age = new UserService().getAge("44030419900122001X");
System.out.println(age);
}
}
3.运行单元测试(测试通过:绿色;测试失败:红色)(加上Test注解后可以看到测试方法的左侧有一个绿色箭头,点击它即可运行测试该方法。点击测试类旁边的绿色箭头也可以,但是所有测试类方法都会运行)
断言
单元测试不报错,就代表代码没问题,测试通过吗?
并不是。只能说明测试方法编写得没有问题,不代表被测试的业务方法的逻辑没有问题。JUnit提供了一些辅助方法,用来帮我们确定被测试的方法是否按照预期的效果正常工作,这种方式称为断言。
| 断言方法 | 描述 |
|---|---|
| Assertions.assertEquals(Object exp,Object act,String msg) | 检查两个值是否相等,不相等就报错 |
| Assertions.assertNotEquals(Object unexp,Object act,String msg) | 检查两个值是否不相等,相等就报错 |
| Assertions.assertNull(Object act,String msg) | 检查对象是否为null,不为null,就报错 |
| Assertions.assertNotNull(Object act,String msg) | 检查对象是否不为null,为null,就报错 |
| Assertions.assertTrue(boolean condition,String msg) | 检查条件是否为true,不为true,就报错 |
| Assertions.assertFalse(boolean condition,String msg) | 检查条件是否为false,不为false,就报错 |
| Assertions.assertThrows(Class expType,Executable exec,String msg) | 检查两个对象引用是否相等,不相等,就报错 |
String msg可以传递,也可以不传递。
@Test
public void testGenderwithAssert() {
String gender = new UserService().getGender("44030419900122001X");
Assertions.assertEquals("男", gender);
}
运行之后,控制台没有任何输出,但基于断言的测试,只要看到绿色就可以了。
如果是错误的,它就会告诉你Expected是什么,Actual是什么。
或者:
@Test
public void testGenderwithAssert() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
new UserService().getGender("44030419900122001");
});
}
这表示我们想看一下业务方法运行时抛出的异常和我们期望的异常是否一致,一致就通过。
常见注解
| 注解 | 说明 | 备注 |
|---|---|---|
| @Test | 测试类中的方法用它修饰才能成为测试方法,才能启动执行 | 单元测试 |
| @ParameterizedTest | 参数化测试的注解(可以让单元测试运行多次,每次运行时仅参数不同) | 用了该注解,就不需要@Test注解了 |
| @ValueSource | 参数化测试的参数来源,赋予测试方法参数 | 与参数化测试注解配合使用 |
| @DisplayName | 指定测试类、测试方法显示的名称(默认为类名、方法名) | |
| @BeforeEach | 用来修饰一个实例方法,该方法会在每一个测试方法执行之前执行一次 | 初始化资源(准备工作) |
| @AfterEach | 用来修饰一个实例方法,该方法会在每一个测试方法执行之后执行一次 | 释放资源(清理工作) |
| @BeforeAll | 用来修饰一个静态方法,该方法会在所有测试方法之前只执行一次 | 初始化资源(准备工作) |
| @AfterAll | 用来修饰一个静态方法,该方法会在所有测试方法之后只执行一次 | 释放资源(清理工作) |
只说一下参数化测试:
@DisplayName("测试用户性别")
@ParameterizedTest
@ValueSource(strings = {
"44030419900122001X", "44030419900122002X"})
public void testGenderwithAssert(String idCard) {
String gender = new UserService().getGender(idCard);
Assertions.assertEquals("男", gender);
}
具体哪个案例通过,哪个没通过,都可以在左侧查看。
企业开发规范
在编写测试方法时,要尽可能地覆盖业务方法中所有可能的情况,尤其是边界值。比如传入的身份证号位数不对,或者传入了null等不符合规范的情况。在运行测试的时候,我们可以选择with Coverage那一项,统计覆盖率。左侧的红绿条也可以看到命中了多少个。
如果不想统计包下所有的类,只统计某个类的覆盖率,可以点击上面的测试类名,选择编辑配置(Edit Configurations),找到代码覆盖率(Code Coverage),可以看到默认是统计包下所有的类。如果想指定某个类,可以把这个包删掉,然后点击加号,选择你想加入的类即可。
我们可以安装通义灵码或trae插件,这样就可以在方法上方看到通义灵码的标识,点击它,选择生成单元测试即可。
Maven依赖范围
在maven项目中,test目录存放单元测试的代码,那能否在main目录中编写单元测试呢?
可以,但是不规范。为了防止不规范情况的出现,我们可以限制在main下只能写主程序,不能写单元测试。
依赖的jar包,在默认情况下可以在任何地方使用,但我们可以通过scope标签设置其作用范围。
作用范围:
主程序范围有效(main文件夹范围内)
测试程序范围有效(test文件夹范围内)
是否参与打包运行(package指令范围内)
| scope值 | 主程序 | 测试程序 | 打包(运行) | 范例 |
|---|---|---|---|---|
| compile(默认) | Y | Y | Y | log4j |
| test | - | Y | - | junit |
| provided | Y | Y | - | servlet-api |
| runtime | - | Y | Y | jdbc驱动 |
在Maven面板双击test,会运行所有规范的测试文件。如果我们在打包的时候不想进行测试,可以点击右上角的跳过测试(logo类似于路面上禁止通行的路牌)
Maven常见问题
依赖爆红,先刷新一下,重新加载maven项目。如果刷新之后依然报错,告诉我们找不到依赖,那么问题可能是第一次下载依赖时网络不好,依赖没有下载完整,但是在仓库中存在依赖的残留文件(xxx.lastUpdated文件)。残留文件不删,再刷新也不会下载依赖。
解决方案:
法一:根据maven依赖的坐标,找到仓库中对应的xxx.lastUpdated文件,删除,删除之后重新加载项目即可(该方法比较繁琐)
法二:在apache-maven-3.9.4下打开cmd,通过命令(del /s *.lastUpdated)批量递归删除指定目录下的xxx.lastUpdated文件,删除之后重新加载项目即可。
如果依然报错,就关闭项目与IDEA ,再重新打开。
也可以直接在maven的目录下写一个.bat批处理脚本,遇到同样问题时,点击一下该脚本就删除完了。