Day03 Maven基础(核心)
什么是Maven
Maven是一款用于管理和构建Java项目的工具,是apache旗下的一个开源项目。
Apache 软件基金会,成立于1999年7月,是目前世界上最大的最受欢迎的开源软件基金会,也是一个专门为支持开源项目而生的非盈利性组织。

Maven的作用





- Maven核心
- Maven概述
- IDEA集成Maven
- 依赖管理
- 单元测试
- Maven进阶
- 分模块设计
- 继承
- 聚合
- 私服
1. Maven概述
1.1 介绍
- ApacheMaven是一个项目管理和构建工具,它基于项目对象模型(POM project object model)的概念,通过一小段描述信息(pom.xml)来管理项目的构建。
- 作用:
- 方便的依赖管理
- 标准的项目构建流程
- 统一的项目结构
- 官网:http://maven.apache.org/
- 仓库:用于存储资源,管理各种jar包。
- 本地仓库:自己计算机上的一个目录。
- 中央仓库:由Maven团队维护的全球唯一的。仓库地址:https://repo1.maven.org/maven2/
- 远程仓库(私服):一般由公司团队搭建的私有仓库。


1.2 安装
下载 apache-maven-3.9.4-bin.zip 解压到D:\develop\apache-maven-3.9.4\
清华镜像:https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.9.11/binaries/apache-maven-3.9.11-bin.zip安装目录新建mvn_repo,配置本地仓库:修改 conf/settings.xml 中的
<localRepository>为一个指定目录。<localRepository>D:\develop\apache-maven-3.9.4\mvn_repo</localRepository>配置阿里云私服:修改 conf/settings.xml 中的
<mirrors>标签,为其添加如下子标签:<mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror>配置环境变量: MAVEN_HOME 为maven的解压目录,并将其bin目录加入PATH环境变量。
cmd下 mvn -v


2. IDEA集成Maven
2.1 IDEA配置Maven环境(全局)

全局配置需关闭Idea重启
2.2 创建Maven项目
① 创建本课程的源代码工作目录 G:\workspace\duSSM\web-ai-code 其中duSSM为个性化目录
② 创建Java空项目 web-ai-project01
③ 创建Maven模块 maven-project01
④ 创建 Helloworld.java并运行


2.3 Maven坐标
什么是坐标?
- Maven 中的坐标是资源(jar)的唯一标识,通过该坐标可以唯一定位资源位置。
- 使用坐标来定义本项目或引入项目中需要的依赖。

Maven 坐标主要组成
- groupId:定义当前Maven项目隶属组织名称(通常是域名反写,例如:com.itheima)
- artifactId:定义当前Maven项目名称(通常是模块名称,例如 order-service、goods-service)
- version:定义当前项目版本号
- SNAPSHOT: 功能不稳定、尚处于开发中的版本,即快照版本
- RELEASE: 功能趋于稳定、当前更新停止,可以用于发行的版本
- 默认(没有SNAPSHOT、RELEASE):也是稳定、发行的版本

2.4 导入Maven项目
先将要导入的maven项目复制到你的项目目录下
方式一:File -> Project Structure -> Modules -> Import Module -> 选择maven项目的pom.xml。
方式二:Maven面板 -> +(Add Maven Projects) -> 选择maven项目的pom.xml。


3. 依赖管理
3.1 依赖配置
依赖:指当前项目运行所需要的jar包,一个项目中可以引入多个依赖。
配置:
- ①在 pom.xml 中编写
<dependencies>标签 - ②在
<dependencies>标签中 使用<dependency>引入坐标 - ③定义坐标的 groupId,artifactId,version
- ④点击刷新按钮,引入最新加入的坐标
- ①在 pom.xml 中编写
- 如果不知道依赖的坐标信息,可以到 https://mvnrepository.com/ 中搜索。
- 排除依赖:指主动断开依赖的资源,被排除的资源无需指定版本。



3.2 生命周期
Maven的生命周期就是为了对所有的maven项目构建过程进行抽象和统一。
Maven中有3套相互独立的生命周期:
- clean:清理工作。
- default:核心工作,如:编译、测试、打包、安装、部署等。
- site:生成报告、发布站点等。
每套生命周期包含一些阶段(phase),阶段是有顺序的,后面的阶段依赖于前面的阶段。

五个主要生命周期阶段:
- clean:移除上一次构建生成的文件
- compile:编译项目源代码
- test:使用合适的单元测试框架运行测试(junit)
- package:将编译后的文件打包,如:jar、war等
- install:安装项目到本地仓库
在同一套生命周期中,当运行后面的阶段时,前面的阶段都会运行。

执行指定生命周期的两种方式:
- 在idea中,右侧的maven工具栏,选中对应的生命周期,双击执行。
- 在命令行中,通过命令执行。



4. 单元测试
测试概述
- 测试:是一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。
- 阶段划分:单元测试、集成测试、系统测试、验收测试。
- 测试方法:白盒测试、黑盒测试 及 灰盒测试。
| 单元测试 | 集成测试 | 系统测试 | 验收测试 | |
|---|---|---|---|---|
| 介绍 | 对软件的基本组成单位进行测试,最小测试单位。 | 将已分别通过测试的单元,按设计要求组合成系统或子系统,再进行的测试 | 对已经集成好的软件系统进行彻底的测试。 | 交付测试,是针对用户需求、业务流程进行的正式的测试。 |
| 目的 | 检验软件基本组成单位的正确性。 | 检查单元之间的协作是否正确。 | 验证软件系统的正确性、性能是否满足指定的要求。 | 验证软件系统是否满足验收标准。 |
| 人员 | 开发人员 | 开发人员 | 测试人员 | 客户/需求方 |

| 方法 | 描述 |
|---|---|
| 白盒 | 清楚软件内部结构、代码逻辑。用于验证代码、逻辑正确性。 |
| 黑盒 | 不清楚软件内部结构、代码逻辑。用于验证软件的功能、兼容性等方面。 |
| 灰盒 | 结合了白盒测试和黑盒测试的特点,既关注软件的内部结构又考虑外部表现(功能)。 |

4.1 快速入门
- 单元测试:就是针对最小的功能单元(方法),编写测试代码对其正确性进行测试。
- JUnit:最流行的Java测试框架之一,提供了一些功能,方便程序进行单元测试(第三方公司提供)。
- main方法测试:
- ①测试代码与源代码未分开,难维护
- ②一个方法测试失败,影响后面方法
- ③无法自动化测试,得到测试报告
- JUnit单元测试
- ①测试代码与源代码分开,便于维护
- ②可根据需要进行自动化测试
- ③可自动分析测试结果,产出测试报告



【案例】使用JUnit,对UserService中业务方法进行单元测试。
- ①在pom.xml中,引入JUnit的依赖。
- ②在main/java目录下,创建业务类 cn/zjy/UserService.java
- ③在test/java目录下,创建测试类cn/zjy/UserServiceTest.java,并编写对应的测试方法,并在方法上声明@Test注解。
④运行单元测试 (测试通过:绿色;测试失败:红色)。
pom.xml
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.1</version>
</dependency>
cn/zjy/UserService.java
package cn.zjy;
import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;
public class UserService {
/**
* 给定一个身份证号, 计算出该用户的年龄
* @param idCard 身份证号
*/
public Integer getAge(String idCard){
if (idCard == null || idCard.length() != 18) {
throw new IllegalArgumentException("无效的身份证号码");
}
String birthday = idCard.substring(6, 14);
LocalDate parse = LocalDate.parse(birthday, DateTimeFormatter.ofPattern("yyyyMMdd"));
return Period.between(parse, LocalDate.now()).getYears();
}
/**
* 给定一个身份证号, 计算出该用户的性别
* @param idCard 身份证号
*/
public String getGender(String idCard){
if (idCard == null || idCard.length() != 18) {
throw new IllegalArgumentException("无效的身份证号码");
}
return Integer.parseInt(idCard.substring(16,17)) % 2 == 1 ? "男" : "女";
}
}
cn/zjy/UserServiceTest.java
package cn.zjy;
import org.junit.jupiter.api.Test;
public class UserServiceTest {
@Test
public void testGetAge(){
Integer age = new UserService().getAge("110002200505091218");
System.out.println(age);
}
}


4.2 断言
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) | 检查程序运行抛出的异常,是否符合预期。 |
上述方法形参中的最后一个参数 msg,表示错误提示信息,可以不指定(有对应的重载方法)。

修改 cn/zjy/UserService.java 性别逻辑
/**
* 给定一个身份证号, 计算出该用户的性别
* @param idCard 身份证号
*/
public String getGender(String idCard){
if (idCard == null || idCard.length() != 18) {
throw new IllegalArgumentException("无效的身份证号码");
}
//return Integer.parseInt(idCard.substring(16,17)) % 2 == 1 ? "男" : "女";
return Integer.parseInt(idCard.substring(16,17)) % 2 == 0 ? "男" : "女";
}
创建 cn/zjy/UserServiceTest.java 断言性别测试
@Test
public void testGenderWithAssert() {
UserService userService = new UserService();
String gender = userService.getGender("100000200010011011");
//断言
// Assertions.assertEquals("男", gender);
Assertions.assertEquals("男", gender, "性别获取错误有问题");
}

修改 cn/zjy/UserService.java 性别逻辑正确
/**
* 给定一个身份证号, 计算出该用户的性别
* @param idCard 身份证号
*/
public String getGender(String idCard){
if (idCard == null || idCard.length() != 18) {
throw new IllegalArgumentException("无效的身份证号码");
}
return Integer.parseInt(idCard.substring(16,17)) % 2 == 1 ? "男" : "女";
}
创建 cn/zjy/UserServiceTest.java 断言性别测试2
@Test
public void testGenderWithAssert2() {
UserService userService = new UserService();
//断言
Assertions.assertThrows(IllegalArgumentException.class, () -> {
userService.getGender(null);
});
}



4.3 常见注解
在JUnit中还提供了一些注解,还增强其功能,常见的注解有以下几个:
| 注解 | 说明 | 备注 |
|---|---|---|
| @Test | 测试类中的方法用它修饰才能成为测试方法,才能启动执行 | 单元测试 |
| @ParameterizedTest | 参数化测试的注解 (可以让单个测试运行多次,每次运行时仅参数不同) | 用了该注解,就不需要@Test注解了 |
| @ValueSource | 参数化测试的参数来源,赋予测试方法参数 | 与参数化测试注解配合使用 |
| @DisplayName | 指定测试类、测试方法显示的名称 (默认为类名、方法名) | |
| @BeforeEach | 用来修饰一个实例方法,该方法会在每一个测试方法执行之前执行一次。 | 初始化资源(准备工作) |
| @AfterEach | 用来修饰一个实例方法,该方法会在每一个测试方法执行之后执行一次。 | 释放资源(清理工作) |
| @BeforeAll | 用来修饰一个静态方法,该方法会在所有测试方法之前只执行一次。 | 初始化资源(准备工作) |
| @AfterAll | 用来修饰一个静态方法,该方法会在所有测试方法之后只执行一次。 | 释放资源(清理工作) |

在 cn/zjy/UserServiceTest.java 添加@BeforeAll、@AfterAll、@BeforeEac、@AfterEach四种注解的方法
package cn.zjy;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.*;
public class UserServiceTest {
@BeforeAll //在所有的单元测试方法运行之前,运行一次
public static void beforeAll() {
System.out.println("--- before All 所有的单元测试方法运行之前,运行一次 ---");
}
@AfterAll //在所有的单元测试方法运行之后,运行一次
public static void afterAll() {
System.out.println("--- after All 所有的单元测试方法运行之后,运行一次 ---");
}
@BeforeEach //在每一个单元测试方法运行之前,都会运行一次
public void beforeEach() {
System.out.println("\n === before Each 每一个单元测试方法运行之前,都会运行一次");
}
@AfterEach //在每一个单元测试方法运行之后,都会运行一次
public void afterEach() {
System.out.println(" === after Each. 每一个单元测试方法运行之后,都会运行一次");
}
@Test
public void testGetAge() {
Integer age = new UserService().getAge("110002200505091218");
System.out.println(age);
}
@Test
public void testGetGender() {
UserService userService = new UserService();
String gender = userService.getGender("100000200010011011");
System.out.println(gender);
}
/**
* 断言
*/
@Test
public void testGenderWithAssert() {
UserService userService = new UserService();
String gender = userService.getGender("100000200010011011");
//断言
// Assertions.assertEquals("男", gender);
Assertions.assertEquals("男", gender, "性别获取错误有问题");
}
@Test
public void testGenderWithAssert2() {
UserService userService = new UserService();
//断言
Assertions.assertThrows(IllegalArgumentException.class, () -> {
userService.getGender(null);
});
}
@Test
public void testGenderWithAssert3() {
UserService userService = new UserService();
//断言
Assertions.assertThrows(NullPointerException.class, () -> {
userService.getGender(null);
});
}
}

参数化测试方法
在 cn/zjy/UserServiceTest.java 注释@BeforeAll、@AfterAll、@BeforeEac、@AfterEach方法添加参数化方法和@DisplayName
package cn.zjy;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@DisplayName("用户信息测试类")
public class UserServiceTest {
...
// 参数化测试
@DisplayName("测试用户性别参数化测试")
@ParameterizedTest
@ValueSource(strings ={"100000200010011011","100000200010011031","100000200010011051"})
public void testGetGender2(String idCard) {
UserService userService = new UserService();
String gender = userService.getGender(idCard);
//断言
Assertions.assertEquals("男", gender);
}


单元测试-企业开发规范
原则:编写测试方法时,要尽可能的覆盖业务方法中所有可能的情况(尤其是边界值)。

cn/zjy/UserService2Test.java
package cn.zjy;
import org.junit.jupiter.api.*;
@DisplayName("用户信息测试类")
public class UserService2Test {
private UserService userService;
@BeforeEach
public void setUp() {
userService = new UserService();
}
/**
*测试获取性别-null
*/
@Test
@DisplayName("获取性别-null值")
public void testGetGender1(){
Assertions.assertThrows(IllegalArgumentException.class, ()-> {
userService.getGender(null);
});
}
/**
*测试获取性别-''
*/
@Test
@DisplayName("获取性别-空串")
public void testGetGender2() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
userService.getGender("");
});
}
/**
*测试获取性别-长度不足
*/
@Test
@DisplayName("获取性别-长度不足")
public void testGetGender3(){
Assertions.assertThrows(IllegalArgumentException.class, () ->{
userService.getGender("110");
});
}
/**
*测试获取性别-超出长度
*/
@Test
@DisplayName("获取性别-长度超出")
public void testGetGender4() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
userService.getGender("10000020001001101111Q0");
});
}
/**
* 测试获取性别-正常:男
*/
@Test
@DisplayName("获取性别-正常男性身份证")
public void testGetGender5() {
String gender = userService.getGender("100000200010011011");
Assertions.assertEquals("男", gender);
}
/**
* 测试获取性别-正常:女
*/
@Test
@DisplayName("获取性别-正常女性身份证")
public void testGetGender6() {
String gender = userService.getGender("100000200010011021");
Assertions.assertEquals("女", gender);
}
// ------- 测试获取年龄 -----------
/**
测试获取年龄-正常
*/
@Test
@DisplayName("获取年龄-正常身份证")
public void testGetAge() {
Integer age = userService.getAge("100000200010011011");
Assertions.assertEquals(24, age);
}
/**
* 测试获取年龄-null值
*/
@Test
@DisplayName("获取年龄-null值")
public void testGetAge2() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
userService.getAge(null);
});
}
/**
*测试获取年龄-长度超
*/
@Test
@DisplayName("获取年龄-长度超长")
public void testGetAge3(){
Assertions.assertThrows(IllegalArgumentException.class, () ->{
userService.getAge("10000020001000200000020001");
});
}
/**
*测试获取年龄-长度不足
*/
@Test
@DisplayName("获取年龄-长度不足")
public void testGetAge4(){
Assertions.assertThrows(IllegalArgumentException.class, () ->{
userService.getAge("100000200011");
});
}
}


基于AI,测试UserService中的getGender方法


cn/zjy/UserServiceAiTest.java
package cn.zjy;
import org.junit.jupiter.api.*;
/**
1. 被测函数分析
- **功能**:根据18位身份证号码,提取第17位数字(从0开始计数为第16~17位),判断奇偶性,返回性别。
- **输入参数**:
- `idCard`: 长度必须为18的字符串,否则抛出 `IllegalArgumentException`
- **输出结果**:
- 若第17位是奇数 → 返回 `"男"`
- 若第17位是偶数 → 返回 `"女"`
2. 分支分析
| 条件 | 分支路径 |
|------|----------|
| `idCard == null` | 抛出异常 |
| `idCard.length() != 18` | 抛出异常 |
| 第17位字符是奇数 | 返回"男" |
| 第17位字符是偶数 | 返回"女" |
3. 测试用例设计
| 用例编号 | 输入值 | 预期行为 | 说明 |
|----------|--------|-----------|------|
| TC01 | null | 抛出异常 | 空指针检查 |
| TC02 | "" | 抛出异常 | 空字符串 |
| TC03 | "123456" | 抛出异常 | 长度不足 |
| TC04 | "12345678901234567890" | 抛出异常 | 长度超出 |
| TC05 | "100000200010011011" | 返回"男" | 第17位是1(奇数) |
| TC06 | "100000200010011021" | 返回"女" | 第17位是2(偶数) |
1. 被测函数分析:getAge(String idCard)
该方法用于根据18位身份证号码计算用户年龄。其逻辑如下:
输入参数:String idCard,必须为18位有效身份证号。
异常处理:
如果 idCard == null 或者长度不等于18,则抛出 IllegalArgumentException。
核心逻辑:
提取身份证中第7到14位(共8位)作为出生日期字符串(格式为 yyyyMMdd)。
使用 LocalDate.parse() 解析该字符串为 LocalDate 类型。
使用 Period.between() 计算当前日期与出生日期之间的年份差,返回年龄。
2. 分支分析
| 条件 | 是否满足条件 | 行为 |
| idCard == null | 是 | 抛出异常 |
| idCard.length() != 18 | 是 | 抛出异常 |
| idCard.length() == 18 且格式正确 | 是 | 正常解析并返回年龄 |
| 出生日期非法(如2月30日) | 是 | 抛出 DateTimeParseException |
3. 测试用例设计
| 测试编号 | 输入 | 预期行为 | 测试目的 |
| TC01 | null | 抛出异常 | 空值校验 |
| TC02 | "" | 抛出异常 | 空串校验 |
| TC03 | "123456" | 抛出异常 | 长度不足 |
| TC04 | "12345678901234567890" | 抛出异常 | 长度超出 |
| TC05 | "100000200010011011" | 返回年龄24(假设当前是2024年) | 正常流程 |
| TC06 | "100000200002291015" | 抛出异常 | 非法日期(闰年2月29日但非闰年) |
*/
@DisplayName("用户测试类")
public class UserServiceAiTest {
private UserService userService;
@BeforeEach
public void setUp() {
// 初始化被测对象
userService = new UserService();
}
/**
* 测试输入为 null 的情况,应抛出 IllegalArgumentException 异常
*/
@Test
@DisplayName("获取性别 - 输入为 null")
public void testGetGender_NullInput_ThrowsException() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
userService.getGender(null);
});
}
/**
* 测试输入为空字符串的情况,应抛出 IllegalArgumentException 异常
*/
@Test
@DisplayName("获取性别 - 输入为空字符串")
public void testGetGender_EmptyString_ThrowsException() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
userService.getGender("");
});
}
/**
* 测试输入长度不足的情况,应抛出 IllegalArgumentException 异常
*/
@Test
@DisplayName("获取性别 - 身份证长度不足")
public void testGetGender_LengthLessThan18_ThrowsException() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
userService.getGender("123456");
});
}
/**
* 测试输入长度超过18的情况,应抛出 IllegalArgumentException 异常
*/
@Test
@DisplayName("获取性别 - 身份证长度超过18")
public void testGetGender_LengthMoreThan18_ThrowsException() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
userService.getGender("12345678901234567890");
});
}
/**
* 测试正常输入且第17位为奇数的情况,应返回"男"
*/
@Test
@DisplayName("获取性别 - 正常输入,第17位为奇数")
public void testGetGender_ValidInput_OddDigit_ReturnsMale() {
String gender = userService.getGender("100000200010011011");
Assertions.assertEquals("男", gender);
}
/**
* 测试正常输入且第17位为偶数的情况,应返回"女"
*/
@Test
@DisplayName("获取性别 - 正常输入,第17位为偶数")
public void testGetGender_ValidInput_EvenDigit_ReturnsFemale() {
String gender = userService.getGender("100000200010011021");
Assertions.assertEquals("女", gender);
}
/**
* 测试获取年龄 - 正常流程(2000年10月1日出生)
*/
@Test
@DisplayName("获取年龄-正常身份证")
public void testGetAge_Normal() {
Integer age = userService.getAge("100000200010011011");
Assertions.assertEquals(24, age); // 2025 - 2000 = 25岁,但生日未到,所以是24岁
}
/**
* 测试获取年龄 - null值
*/
@Test
@DisplayName("获取年龄-null值")
public void testGetAge_Null() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
userService.getAge(null);
});
}
/**
* 测试获取年龄 - 空字符串
*/
@Test
@DisplayName("获取年龄-空串")
public void testGetAge_EmptyString() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
userService.getAge("");
});
}
/**
* 测试获取年龄 - 长度不足
*/
@Test
@DisplayName("获取年龄-长度不足")
public void testGetAge_LengthLessThan18() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
userService.getAge("123456");
});
}
/**
* 测试获取年龄 - 长度超过18
*/
@Test
@DisplayName("获取年龄-长度超出")
public void testGetAge_LengthMoreThan18() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
userService.getAge("12345678901234567890");
});
}
/**
* 测试获取年龄 - 非法日期(如2000年2月29日但不是闰年)
*/
@Test
@DisplayName("获取年龄-非法出生日期")
public void testGetAge_IllegalBirthday() {
Assertions.assertThrows(Exception.class, () -> {
userService.getAge("100000190002291015"); // 1900年不是闰年
});
}
}

4.4 依赖范围

- 依赖的jar包,默认情况下,可以在任何地方使用。可以通过
<scope>…</scope>设置其作用范围。 - 作用范围:
- 主程序范围有效。(main文件夹范围内)
- 测试程序范围有效。(test文件夹范围内)
- 是否参与打包运行。(package指令范围内)
| scope值 | 主程序 | 测试程序 | 打包(运行) | 范例 |
|---|---|---|---|---|
| compile(默认) | Y | Y | Y | log4j |
| test | - | Y | - | junit |
| provided | Y | Y | - | servlet-api |
| runtime | - | Y | Y | jdbc驱动 |


4.5 Maven常见问题
- 产生原因:由于网络原因,依赖没有下载完整导致的,在maven仓库中生成了xxx.lastUpdated文件,该文件不删除,不会再重新下载。
- 解决方案:
- 根据maven依赖的坐标,找到仓库中对应的 xxx.lastUpdated 文件,删除,删除之后重新加载项目即可。
- 通过命令 (del /s *.lastUpdated) 批量递归删除指定目录下的 xxx.lastUpdated 文件,删除之后重新加载项目即可。
- 重新加载依赖,依赖下载了之后,maven面板可能还会报红,此时可以关闭IDEA,重新打开IDEA加载此项目即可。

①②③④⑤⑥⑦⑧⑨⑩