国庆节10月1日
--


讲清 <scope> 的三种作用范围与四种取值(compile / test / provided / runtime 能不能给主程序用、能不能给测试程序用、参不参与打包,配 log4j、junit、servlet-api、jdbc 驱动四个范例),以及依赖下载不完整留下的 xxx.lastUpdated 文件为什么会让下载一直失败、该怎么删(含 del /s *.lastUpdated 与重启 IDEA),全部附本机实测

这是 Maven 这一章的最后一篇,收掉 27、28两篇里一直留着的那个尾巴——pom 里 junit 依赖后面那行 <scope>test</scope> 到底是干什么的(PPT 第 66 页的”依赖范围”),再补上 PPT 第 69 页的 Maven 常见问题:依赖下载不完整留下的 xxx.lastUpdated 文件该怎么处理。
<scope> 是什么#PPT 第 67 页的定义:
依赖范围:依赖的 jar 包,默认情况下,可以在任何地方使用。可以通过
<scope>…</scope>设置其作用范围。
默认情况下一个依赖”到处都能用”,可以通过 <scope> 把它限制在特定场合。这里的”特定场合”就是 PPT 第 67 页列出的三个作用范围:
作用范围:
- 主程序范围有效。(
main文件夹范围内)- 测试程序范围有效。(
test文件夹范围内)- 是否参与打包运行。(
package指令范围内)
翻成大白话:一个依赖的 jar,可能”写主程序的时候能用""写测试的时候能用""打完包运行时还能用”——这三件事分开控制,就是依赖范围。
PPT 第 67 页给了完整的对照表(Y = 有效,- = 无效):
| scope 值 | 主程序 | 测试程序 | 打包(运行) | 范例 |
|---|---|---|---|---|
| compile(默认) | Y | Y | Y | log4j |
| test | - | Y | - | junit |
| provided | Y | Y | - | servlet-api |
| runtime | - | Y | Y | jdbc 驱动 |
把这张表和上面的”三个作用范围”对齐,每条都能讲出道理:
| scope | 三个范围 | 为什么这样设计 |
|---|---|---|
| compile(默认) | 主程序 ✅、测试程序 ✅、打包运行 ✅ | 最普通的依赖(如 log4j 日志):主程序要写日志、测试也要、上线运行时还得有它,所以哪儿都用得上。不写 <scope> 就是它 |
| test | 主程序 ❌、测试程序 ✅、打包运行 ❌ | 只给测试用的东西(如 junit):主程序里根本不该出现 @Test,打包后更不该带着测试框架,只在测试阶段有效 |
| provided | 主程序 ✅、测试程序 ✅、打包运行 ❌ | 由运行环境提供的依赖(如 servlet-api):写代码时要 import 它才能编译、写测试也要它,但真正跑起来时 Tomcat 这类 Web 服务器自己就带了——你打 war 包时再塞一份进去,反而容易和容器自带的版本打架,所以不参与打包 |
| runtime | 主程序 ❌、测试程序 ✅、打包运行 ✅ | 编译时用不到、运行时才用得到的依赖(如 jdbc 驱动):代码里只用 JDBC 的接口(java.sql.*),驱动是在运行时靠反射加载的,所以主程序编译时不需要它,但打包运行必须有 |
PPT 第 67 页给的就是 junit 加上范围的写法:
1<dependency>2 <groupId>org.junit.jupiter</groupId>3 <artifactId>junit-jupiter</artifactId>4 <version>5.9.3</version>5 <scope>test</scope> <!-- 依赖范围:只在测试程序里有效 -->6</dependency><scope> 写在 <dependency> 里面(和 groupId、artifactId、version 平级),值就是 compile、test、provided、runtime 这四个词。
PPT 第 67 页这个例子里 junit 的版本写的是 5.9.3,而 PPT 第 55 页(快速入门)和课程代码 maven-project01/pom.xml 里写的都是 5.9.1——同一份讲义里前后不一致,按课程代码的版本(5.9.1)写就对,可以理解为 PPT 这页只是拿 junit 举”依赖范围”的例子,顺手换了个版本号。
课程代码里那个 junit 依赖就正好是 PPT 快速入门 + 依赖范围两页的合体:
1<!--junit依赖-->2<dependency>3 <groupId>org.junit.jupiter</groupId>4 <artifactId>junit-jupiter</artifactId>5 <version>5.9.1</version>6 <!--依赖范围-->7 <scope>test</scope>8</dependency>| PPT 的问题 | 答案 |
|---|---|
| Maven 的依赖范围如何指定? | <scope>xxx</scope>(写在 <dependency> 里) |
| 常见的取值有哪些? | compile(默认)、test、provided、runtime |
依赖范围不是”写在 pom 里给人看的”,它真的会影响 Maven 的解析结果。本机用一个最小项目把四种取值各配一个依赖,跑 mvn dependency:tree 的真实输出:
本机实测(Maven 3.9.14 / JDK 17)——四个依赖分别是 log4j:log4j:1.2.12(不写 scope)、org.junit.jupiter:junit-jupiter:5.9.1(test)、jakarta.servlet:jakarta.servlet-api:6.0.0(provided)、com.mysql:mysql-connector-j:8.3.0(runtime):
1[INFO] com.itheima:scope-demo:jar:1.0-SNAPSHOT2[INFO] +- log4j:log4j:jar:1.2.12:compile3[INFO] +- org.junit.jupiter:junit-jupiter:jar:5.9.1:test4[INFO] | +- org.junit.jupiter:junit-jupiter-api:jar:5.9.1:test5[INFO] | | +- org.opentest4j:opentest4j:jar:1.2.0:test6[INFO] | | +- org.junit.platform:junit-platform-commons:jar:1.9.1:test7[INFO] | | \- org.apiguardian:apiguardian-api:jar:1.1.2:test8[INFO] | +- org.junit.jupiter:junit-jupiter-params:jar:5.9.1:test9[INFO] | \- org.junit.jupiter:junit-jupiter-engine:jar:5.9.1:test10[INFO] | \- org.junit.platform:junit-platform-engine:jar:1.9.1:test11[INFO] +- jakarta.servlet:jakarta.servlet-api:jar:6.0.0:provided12[INFO] \- com.mysql:mysql-connector-j:jar:8.3.0:runtime13[INFO] \- com.google.protobuf:protobuf-java:jar:3.25.1:runtime注意每行坐标最后那一段就是 scope:不写 scope 的 log4j 显示成 :compile(这就是”默认值”的由来);junit 连同它带进来的一串传递性依赖全是 :test;servlet-api 是 :provided;jdbc 驱动(连它的传递依赖 protobuf)是 :runtime。传递性依赖会继承 scope 这一点很重要:写一个 test 范围的 junit,Maven 不会把它内部的 api/engine/params 变成主程序可用的东西。
test 范围的依赖,主程序真的用不了#“主程序 ❌“是什么意思?——在 src/main/java 里的代码,根本引用不到这个 jar。本机实测:在 main 目录里加一个类、import org.junit.jupiter.api.Test,然后编译:
本机实测(Maven 3.9.14 / JDK 17)——scope=test 的 junit,在 src/main/java 里引用它:
1package com.itheima;2
3import org.junit.jupiter.api.Test; // ← 这一行在 main 目录里就过不了编译4
5public class JunitInMainDemo {6 @Test7 public void demo(){ }8}运行 mvn compile 的真实输出:
1[INFO] Compiling 2 source files with javac [debug target 17] to target\classes2[ERROR] COMPILATION ERROR :3[ERROR] /C:/.../src/main/java/com/itheima/JunitInMainDemo.java:[3,29] 程序包org.junit.jupiter.api不存在4[ERROR] /C:/.../src/main/java/com/itheima/JunitInMainDemo.java:[10,6] 找不到符号5[ERROR] 符号: 类 Test6[ERROR] 位置: 类 com.itheima.JunitInMainDemo7[ERROR] BUILD FAILURE8[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.15.0:compile (default-compile) on project junit-demo: Compilation failure把那个类删掉再编译,立刻 BUILD SUCCESS。报错原文说得很直白:程序包org.junit.jupiter.api不存在——test 范围的 jar 不在主程序的编译 classpath 上。这条实测同时解释了 27 篇里 PPT 第 65 页那个问答的深层原因:“能不能在 main 目录里写单元测试?可以,但是不规范”——只有把 junit 写成默认的 compile 范围才”可以”(连依赖范围都被迫放宽了,显然不划算),而按规范写成 test 之后,main 目录里连 JUnit 的类都编译不过。
“主程序 / 测试程序 / 打包运行”这三个范围,在 Maven 里其实对应三条 classpath(编译/运行时要加载哪些 jar 的清单)。本机用 maven-dependency-plugin 把三条清单都打出来(-DincludeScope=compile 是主程序编译时、runtime 是打包运行后、test 是测试程序,默认值):
本机实测(Maven 3.9.14 / JDK 17),用的是上面那个”四个依赖各一种 scope”的项目,只保留 jar 文件名:
| 命令 | 打出来的 jar(主程序编译 / 打包运行 / 测试程序) |
|---|---|
mvn dependency:build-classpath -DincludeScope=compile | log4j-1.2.12.jar、jakarta.servlet-api-6.0.0.jar |
mvn dependency:build-classpath -DincludeScope=runtime | log4j-1.2.12.jar、mysql-connector-j-8.3.0.jar、protobuf-java-3.25.1.jar |
mvn dependency:build-classpath -DincludeScope=test | 上面全部 12 个 jar(四类依赖都在) |
对着 PPT 的表格逐格核对,四种取值和三条 classpath 完全对得上:
| scope | 主程序(compile 清单) | 测试程序(test 清单) | 打包运行(runtime 清单) | 对上了吗 |
|---|---|---|---|---|
| compile(log4j) | ✅ 有 | ✅ 有 | ✅ 有 | Y / Y / Y |
| test(junit) | ❌ 没有 | ✅ 有 | ❌ 没有 | - / Y / - |
| provided(servlet-api) | ✅ 有 | ✅ 有 | ❌ 没有 | Y / Y / - |
| runtime(jdbc 驱动) | ❌ 没有 | ✅ 有 | ✅ 有 | - / Y / Y |
三条清单的差别一眼可见:servlet-api 只出现在前两条(编译、测试),jdbc 驱动只出现在后两条(测试、运行),junit 只在测试那条里。这就是”依赖范围”三个字的实际效果,也是 PPT 表格里那些 Y 和 - 的由来。
PPT 第 70 页就讲一个问题(页面标题”05 Maven 常见问题”),先把”现象 + 原因”说清楚:
产生原因:由于网络原因,依赖没有下载完整导致的,在 maven 仓库中生成了
xxx.lastUpdated文件,该文件不删除,不会再重新下载。
然后给了三条解决方案:
解决方案:
- 根据 maven 依赖的坐标,找到仓库中对应的
xxx.lastUpdated文件,删除,删除之后重新加载项目即可。- 通过命令(
del /s *.lastUpdated)批量递归删除指定目录下的xxx.lastUpdated文件,删除之后重新加载项目即可。- 重新加载依赖,依赖下载了之后,maven 面板可能还会报红,此时可以关闭 IDEA,重新打开 IDEA 加载此项目即可。
PPT 第 70 页配的截图就是第 3 条说的”maven 面板还会报红”的样子:

spring-context 下面挂着的 spring-aop、spring-beans、spring-core、spring-expression、micrometer-observation 这些传递性依赖都红着);列表里 org.junit.jupiter:junit-jupiter:5.9.3 (test) 后面的 (test) 就是这一篇讲的依赖范围标记——这也是”依赖下载出问题时,先看这个面板”的原因讲清楚这一条,后面所有操作才有根:
xxx.lastUpdated 文件。它的作用是记录”这个 artifact 我试过了、没拿到”;.lastUpdated 有 → 它认为”最近刚试过、还是别试了”,于是直接报”缓存过的失败”,不再联网重试;lastUpdated 制造出来,再看它怎么”卡住”下载#本机用一个”坐标根本不存在”的依赖(com.example.nonexist:nope-artifact:1.0.0)+ 一个干净的临时本地仓库,把这个过程完整复现了一遍。
第一次构建(正常联网):Maven 去阿里云镜像找这个坐标,找不到,构建失败:
1Downloading from aliyunmaven: https://maven.aliyun.com/repository/public/com/example/nonexist/nope-artifact/1.0.0/nope-artifact-1.0.0.pom2[WARNING] The POM for com.example.nonexist:nope-artifact:jar:1.0.0 is missing, no dependency information available3Downloading from aliyunmaven: https://maven.aliyun.com/repository/public/com/example/nonexist/nope-artifact/1.0.0/nope-artifact-1.0.0.jar4[INFO] BUILD FAILURE5[ERROR] Failed to execute goal on project lastupdated-demo: Could not resolve dependencies for project com.itheima:lastupdated-demo:jar:1.0-SNAPSHOT6[ERROR] dependency: com.example.nonexist:nope-artifact:jar:1.0.0 (compile)7[ERROR] Could not find artifact com.example.nonexist:nope-artifact:jar:1.0.0 in aliyunmaven (https://maven.aliyun.com/repository/public)失败之后,去本地仓库里对应坐标的目录看,多出来两个 .lastUpdated 文件:
1C:\...\tmprepo\com\example\nonexist\nope-artifact\1.0.0\2├── nope-artifact-1.0.0.jar.lastUpdated ← 试过 jar、没拿到3└── nope-artifact-1.0.0.pom.lastUpdated ← 试过 pom、没拿到它们其实就是一个 properties 文件,里面记着”什么时候、在哪个仓库试过、失败了”:
1#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.2#Tue Sep 29 15:06:09 CST 20263https\://maven.aliyun.com/repository/public/.lastUpdated=17906655690084https\://maven.aliyun.com/repository/public/.error=第二次构建(什么都不删)——报错直接变成”上次试过了,不再重试”:
1[INFO] BUILD FAILURE2[ERROR] Failed to execute goal on project lastupdated-demo: Could not resolve dependencies for project com.itheima:lastupdated-demo:jar:1.0-SNAPSHOT3[ERROR] dependency: com.example.nonexist:nope-artifact:jar:1.0.0 (compile)4[ERROR] com.example.nonexist:nope-artifact:jar:1.0.0 was not found in https://maven.aliyun.com/repository/public during a previous attempt. This failure was cached in the local repository and resolution is not reattempted until the update interval of aliyunmaven has elapsed or updates are forced最后那行是整篇文章最该记住的英文句子:“was not found … during a previous attempt. This failure was cached in the local repository and resolution is not reattempted until …”——“上次没找到,这个失败被缓存在本地仓库里,在更新间隔过去或强制更新之前不会重新尝试”。日常遇到明明网是通的、坐标也没写错,却怎么刷新都不下载,八成就是它。
del 批量删#方法一(PPT 第 1 条):按坐标找文件删。.lastUpdated 的位置由坐标决定,规律和上一篇里 install 那行日志是同一套:本地仓库 / groupId 的每段 / artifactId / version / 文件名。比如本机真实环境里(之前误写过 spring-contextx、spring-context:9.9.9 这种不存在的坐标)就留下了这样的文件:
1A:\develop\maven\apache-maven-3.9.14\mvn_repo\org\springframework\spring-contextx\6.1.4\spring-contextx-6.1.4.jar.lastUpdated2A:\develop\maven\apache-maven-3.9.14\mvn_repo\org\springframework\spring-context\9.9.9\spring-context-9.9.9.pom.lastUpdated顺着 org/springframework/spring-contextx/6.1.4/ 这串目录就能找到(每个目录名对应坐标的一段),删掉再重新加载项目即可。
方法二(PPT 第 2 条):命令行批量删。整个仓库找起来太慢时,用 PPT 给的这条命令:
1del /s *.lastUpdateddel 是删除、/s 是递归(当前目录及下面所有子目录)、*.lastUpdated 是匹配所有这类文件。课程的安装包目录里就放了一个现成的 del.bat,内容只有这一行(双击即可执行)。
本机实测(Windows 11 / Git Bash):在仓库目录执行 cmd /c "del /s *.lastUpdated",它会把删掉的每一个文件都打印出来,真实输出如下:
1删除文件 - C:\...\tmprepo\com\example\nonexist\nope-artifact\1.0.0\nope-artifact-1.0.0.jar.lastUpdated2删除文件 - C:\...\tmprepo\com\example\nonexist\nope-artifact\1.0.0\nope-artifact-1.0.0.pom.lastUpdated删完再数一遍,匹配到的 .lastUpdated 文件数从 2 变成 0。删完必须”重新加载项目”(IDEA 里点 Maven 面板的刷新按钮,命令行随便跑个阶段):本机实测删除后重新构建,输出里又出现了 Downloading from aliyunmaven: .../nope-artifact-1.0.0.pom ——说明它真的重新去试了(这个坐标本身不存在,所以最终还是失败,但”卡死不重试”的状态已经解开;如果是网络抖动造成的失败,这一次就下载成功了)。
PPT 第 3 条说的就是这种情况:依赖其实已经下载好了,Maven 面板却还挂着红波浪线(上图那种)。原因是 IDEA 的界面状态没跟着更新——解决办法就是关闭 IDEA、重新打开项目(或者 File → Invalidate Caches / Restart),让它重新索引一遍依赖。所以完整的处理顺序是:
1① 看报错、确认是"下载失败/缓存失败"这一类2 ↓3② 删掉对应的 xxx.lastUpdated(按坐标找,或 del /s *.lastUpdated 批量删)4 ↓5③ 重新加载项目(IDEA 点 Maven 面板刷新按钮;命令行跑 mvn compile 等任一阶段)→ 让它重新联网下载6 ↓7④ 依赖已经下载了、面板还报红 → 关掉 IDEA 重新打开(或 Invalidate Caches / Restart)| 问题 | 答案 |
|---|---|
| 依赖范围是什么? | 依赖的 jar 包默认可以在任何地方使用,通过 <scope>…</scope> 设置它的作用范围 |
| 三个作用范围是? | ① 主程序范围有效(main 文件夹内);② 测试程序范围有效(test 文件夹内);③ 是否参与打包运行(package 指令范围内) |
| 四种取值各是什么? | compile(默认):主 Y、测 Y、打包 Y,范例 log4j;test:主 -、测 Y、打包 -,范例 junit;provided:主 Y、测 Y、打包 -,范例 servlet-api;runtime:主 -、测 Y、打包 Y,范例 jdbc 驱动 |
| 依赖范围怎么写? | 在 <dependency> 里加 <scope>test</scope>(值与 groupId/artifactId/version 平级);junit 这种测试框架必须写 <scope>test</scope> |
| 怎么验证范围生效了? | mvn dependency:tree:每行坐标最后一段就是 scope(:compile / :test / :provided / :runtime),且传递性依赖会继承 scope;本机实测 scope=test 的 junit 在 src/main/java 里引用会编译失败(程序包org.junit.jupiter.api不存在) |
xxx.lastUpdated 是怎么来的? | 网络原因导致依赖没有下载完整,Maven 在本地仓库里生成 xxx.lastUpdated 记录”这次失败”;该文件不删除,就不会再重新下载(第二次构建会报 “failure was cached in the local repository and resolution is not reattempted…”) |
| 怎么处理? | ① 按坐标找到仓库里对应的 xxx.lastUpdated 删除,然后重新加载项目;② 用 del /s *.lastUpdated 批量递归删除(课程安装包里就有写好的 del.bat),再重新加载;③ 依赖下载好了 Maven 面板还报红时,关闭 IDEA 重新打开(或 Invalidate Caches / Restart) |
<scope>…</scope> 设置其作用范围main 文件夹范围内);② 测试程序范围有效(test 文件夹范围内);③ 是否参与打包运行(package 指令范围内)<scope> 的写法位置:写在 <dependency> 标签里面,与 groupId、artifactId、version 平级,值就是 compile / test / provided / runtime 四个词;junit 必须写 <scope>test</scope>mvn dependency:tree 的每行坐标最后一段就是 scope(:compile、:test、:provided、:runtime),传递性依赖会继承 scope;本机实测 scope=test 的 junit 在 src/main/java 里引用会编译失败(报 程序包org.junit.jupiter.api不存在)<scope>xxx</scope>;常见取值 → compile(默认)、test、provided、runtimexxx.lastUpdated 的产生原因:由于网络原因,依赖没有下载完整,在 maven 仓库中生成了 xxx.lastUpdated 文件;该文件不删除,不会再重新下载(第二次构建报 “This failure was cached in the local repository and resolution is not reattempted…”)xxx.lastUpdated 文件,删除,删除之后重新加载项目即可(文件位置规律是”本地仓库 / groupId 的每段 / artifactId / version / 文件名”);也可以改用命令 del /s *.lastUpdated 批量递归删除(/s = 递归子目录,课程安装包里有现成的 del.bat),删完同样要重新加载项目 2-1 给 junit 依赖加上依赖范围
项目里已经引入了 JUnit(org.junit.jupiter:junit-jupiter:5.9.1),但它现在”哪儿都能用”,包括 src/main/java。请在练习文件的 pom 骨架里:
src/main/java 里还能不能 import org.junit.jupiter.api.Test?为什么?
(练习文件 test_29_junit依赖范围.xml 里已经给了 pom 骨架和写作区。)一级 · 思路:测试框架这种东西,“上线打包”肯定不要;“主程序”也不该用它——所以四个取值里,只有”只在测试程序有效”的那一个合适
二级 · 方法:在 <dependency> 里加 <scope> 标签,值是四个候选值之一
三级 · 骨架:<dependency> ... <scope>____</scope> </dependency>
1<dependencies>2 <!--junit依赖-->3 <dependency>4 <groupId>org.junit.jupiter</groupId>5 <artifactId>junit-jupiter</artifactId>6 <version>5.9.1</version>7 <!--依赖范围:test —— 只在测试程序范围有效(主程序不能引用、也不参与打包)-->8 <scope>test</scope>9 </dependency>10</dependencies>test 范围意味着这个 jar 不在主程序的编译 classpath 上,src/main/java 里的类一旦 import 它,编译就会失败。本机实测(Maven 3.9.14 / JDK 17)报错原文是 程序包org.junit.jupiter.api不存在 和 找不到符号 类 Test,最后 BUILD FAILURE;把那个引用删掉立刻 BUILD SUCCESS。 2-2 判断四个依赖该配什么范围
一个 Java Web 项目要用到下面四样东西,请分别判断它们该写哪个 <scope>,并把四个依赖都写进练习文件的 pom 骨架里(每个依赖后面用中文注释说明理由):
log4j:log4j:1.2.12(日志,主程序要写日志、上线运行时也要);org.junit.jupiter:junit-jupiter:5.9.1(单元测试框架);jakarta.servlet:jakarta.servlet-api:6.0.0(Servlet 规范,Tomcat 服务器自己带了);com.mysql:mysql-connector-j:8.3.0(MySQL 的 jdbc 驱动,代码里只用 JDBC 接口、运行时才加载驱动)。
(练习文件 test_29_四个依赖的范围配置.xml 里给了写作区。)一级 · 思路:对每个依赖问三个问题——主程序写代码时要不要它?写测试时要不要它?打包上线后运行时要不要它?三个答案定了,取值就定了
二级 · 方法:要”到处都要”→ 它;要”只有测试要”→ 它;要”只有编译要、运行环境提供”→ 它;要”编译不要、运行要”→ 它
三级 · 骨架:<scope>____</scope>(四个候选值分别对应:主+测+打包 / 只测 / 主+测 / 测+打包)
1<dependencies>2 <!-- 日志:主程序、测试、打包运行都要用 → 默认的 compile(不写 scope 也行) -->3 <dependency>4 <groupId>log4j</groupId>5 <artifactId>log4j</artifactId>6 <version>1.2.12</version>7 <scope>compile</scope>8 </dependency>9
10 <!-- 单元测试框架:只有测试程序用得到 → test -->11 <dependency>12 <groupId>org.junit.jupiter</groupId>13 <artifactId>junit-jupiter</artifactId>14 <version>5.9.1</version>15 <scope>test</scope>16 </dependency>17
18 <!-- Servlet 规范:编译和测试要,运行时由 Tomcat 提供 → provided -->19 <dependency>20 <groupId>jakarta.servlet</groupId>21 <artifactId>jakarta.servlet-api</artifactId>22 <version>6.0.0</version>23 <scope>provided</scope>24 </dependency>25
26 <!-- jdbc 驱动:编译时用不到(代码里只用 java.sql 的接口),运行时才加载 → runtime -->27 <dependency>28 <groupId>com.mysql</groupId>29 <artifactId>mysql-connector-j</artifactId>30 <version>8.3.0</version>31 <scope>runtime</scope>32 </dependency>33</dependencies>核对办法:本机实测(Maven 3.9.14 / JDK 17)跑 mvn dependency:tree,四行分别是 log4j:log4j:jar:1.2.12:compile、org.junit.jupiter:junit-jupiter:jar:5.9.1:test、jakarta.servlet:jakarta.servlet-api:jar:6.0.0:provided、com.mysql:mysql-connector-j:jar:8.3.0:runtime——正好是 PPT 第 67 页表格里那四个范例。
2-3 写一个只依赖 test 范围 jar 的测试类
在练习文件里写一个测试类,验证”junit 只在测试程序里有效”这件事:
getAge("100000200010011011") 返回 25,一个测 getGender("100000200010011011") 返回 “男”(都要用断言);src/main/java 还是 src/test/java)?② 如果把 pom 里 junit 的 <scope>test</scope> 改成 <scope>runtime</scope>,这个测试类还能编译/运行吗?为什么?
(练习文件 test_29_测试类与依赖范围.java 里已经给了写作区。)一级 · 思路:junit 的坐标写在 pom 里、范围是 test;测试类的目录也得和范围对得上——先想”哪个目录算测试程序”
二级 · 方法:测试代码放 src/test/java;断言用 Assertions.assertEquals(预期, 实际, msg);runtime 的含义是”主程序不可用、测试可用、打包运行可用”
三级 · 骨架:@Test public void ____(){ Assertions.assertEquals(25, new UserService().getAge("____"), "年龄校验失败"); }
2-3 测试类:
1package com.itheima;2
3import org.junit.jupiter.api.Assertions;4import org.junit.jupiter.api.Test;5
6public class UserServiceTest {7
8 @Test9 public void testGetAge(){10 Integer age = new UserService().getAge("100000200010011011");11 Assertions.assertEquals(25, age, "年龄校验失败");12 }13
14 @Test15 public void testGetGender(){16 String gender = new UserService().getGender("100000200010011011");17 Assertions.assertEquals("男", gender, "性别校验失败");18 }19}两个问题:
① 放 src/test/java(目录里带 test 的那个)。test 范围的依赖只在”测试程序范围”有效,代码放进 main 目录就属于主程序了,引用 junit 会直接编译失败——本机实测报 程序包org.junit.jupiter.api不存在;而本机另一份实测(Maven 3.9.14 / JDK 17)把同样的测试类放在 src/test/java 跑 mvn test,结果是 Tests run: 2, Failures: 0, Errors: 0 + BUILD SUCCESS。
② 能运行(但没必要这么改)。runtime 的三项是”主程序 -、测试程序 Y、打包运行 Y”——测试程序那一项是 Y,所以测试类照样能编译、能跑;差别在于它会把 junit 带进打包运行的范围(这是设计给 jdbc 驱动那种”运行时才加载”的依赖用的),而 junit 只在测试时有用,写成 runtime 等于白白往运行的清单里塞了一个测试框架,所以规范写法仍然是 <scope>test</scope>。
3-1 给一个 Web 项目配全依赖范围,并亲手验证三种清单 把这一篇的两种知识(依赖范围 + 依赖下载问题的排查)串起来做一遍。场景:一个要打成 war 的 Web 项目,需要四类依赖。要求:
<scope>,每个都用中文注释说明”主程序/测试程序/打包运行”三项分别是有效还是无效;mvn dependency:tree,把输出的四行(含每行末尾的 scope)抄到文件末尾的注释里;mvn dependency:build-classpath -DincludeScope=compile,记下哪一个依赖不见了;换成 -DincludeScope=runtime 再记一次;最后用 -DincludeScope=test 跑一次,说明为什么这一次”全都在”;mvn compile 一直报某个依赖找不到,而且每次报错都很快、看不出在联网下载,写出你的排查步骤(至少三条),并写出**批量删除”下载失败记录”**的那条命令;test_29_综合_依赖范围与排查.xml 里按这 5 步给了写作区。)涉及知识点
| 知识点 | 在这里的应用 |
|---|---|
| 依赖范围三个范围 | 主程序 main、测试程序 test、打包运行 package |
| 四种取值 | compile / test / provided / runtime 与四个范例的对应 |
<scope> 写法 | 写在 <dependency> 里,和坐标三行平级 |
| 依赖树与 classpath | mvn dependency:tree、-DincludeScope=compile/runtime/test |
xxx.lastUpdated | 失败的”缓存记录”为什么不删就不重试 |
| 三条处理方案 | 按坐标删文件 / del /s *.lastUpdated / 重启 IDEA |
一级 · 思路:每个依赖对”主程序 / 测试 / 打包运行”三项打勾,勾的形状决定取值;第 4、5 步是”下载失败”这类问题的固定套路——先删失败记录、再重新加载、最后重启 IDE
二级 · 方法:日志要到处用 → compile;测试框架 → test;容器自带的规范包 → provided;运行时才加载的驱动 → runtime;删失败记录用 del /s *.lastUpdated(或按坐标找 xxx.lastUpdated 删),然后重新加载项目
三级 · 骨架:<scope>____</scope> ×4;排查顺序 = 删 xxx.lastUpdated → 重新加载/构建 → 重启 IDEA
3-1
1<dependencies>2 <!-- 日志:主程序 Y、测试程序 Y、打包运行 Y → compile(默认) -->3 <dependency>4 <groupId>log4j</groupId>5 <artifactId>log4j</artifactId>6 <version>1.2.12</version>7 <scope>compile</scope>8 </dependency>9
10 <!-- 单元测试:主程序 -、测试程序 Y、打包运行 - → test -->11 <dependency>12 <groupId>org.junit.jupiter</groupId>13 <artifactId>junit-jupiter</artifactId>14 <version>5.9.1</version>15 <scope>test</scope>16 </dependency>17
18 <!-- Servlet 规范:主程序 Y、测试程序 Y、打包运行 -(Tomcat 提供)→ provided -->19 <dependency>20 <groupId>jakarta.servlet</groupId>21 <artifactId>jakarta.servlet-api</artifactId>22 <version>6.0.0</version>23 <scope>provided</scope>24 </dependency>25
26 <!-- jdbc 驱动:主程序 -、测试程序 Y、打包运行 Y → runtime -->27 <dependency>28 <groupId>com.mysql</groupId>29 <artifactId>mysql-connector-j</artifactId>30 <version>8.3.0</version>31 <scope>runtime</scope>32 </dependency>33</dependencies>mvn dependency:tree 的四行(本机实测,Maven 3.9.14 / JDK 17):
1[INFO] +- log4j:log4j:jar:1.2.12:compile2[INFO] +- org.junit.jupiter:junit-jupiter:jar:5.9.1:test3[INFO] +- jakarta.servlet:jakarta.servlet-api:jar:6.0.0:provided4[INFO] \- com.mysql:mysql-connector-j:jar:8.3.0:runtime-DincludeScope=compile(主程序编译时):log4j-1.2.12.jar、jakarta.servlet-api-6.0.0.jar → junit 和 jdbc 驱动都不见了(junit 是 test 范围、驱动是 runtime 范围,主程序都用不到);-DincludeScope=runtime(打包运行后):log4j-1.2.12.jar、mysql-connector-j-8.3.0.jar、protobuf-java-3.25.1.jar → servlet-api 不见了(provided 不参与打包运行,运行时由 Tomcat 提供),jdbc 驱动回来了;-DincludeScope=test(测试程序):四类依赖全都在(12 个 jar)——因为 test 是包含最广的那条清单,它把 compile / provided / runtime 的依赖都算进来,这正是 PPT 表格里”测试程序”那一列四种取值全是 Y 的原因。xxx.lastUpdated 文件删掉(路径规律是”本地仓库 / groupId 每段 / artifactId / version / 文件名”);③ 用命令 del /s *.lastUpdated 在本地仓库目录批量递归删除(课程安装包里有现成的 del.bat),然后重新加载项目(IDEA 点刷新按钮,命令行跑 mvn compile 等任一阶段);④ 顺带确认网络和坐标:本地仓库没有的依赖要联网下载,坐标写错会报 Could not find artifact ...;⑤ 删完重试之后不再报 “cached”、而是重新出现 Downloading from aliyunmaven: ...,说明确实联网重试了(本机实测删除后重新构建就是这个现象)。File → Invalidate Caches / Restart)。Maven 这一章到这里就结束了:从”是什么”(23)→ 安装配置(24)→ 创建项目与坐标(25)→ 依赖管理与生命周期(26)→ 单元测试(27、28)→ 依赖范围与常见问题(这一篇)。
如果你喜欢,那么欢迎来到我的世界!
了解更多暂未播放



"所有的相遇都是久别重逢。"—— 未知


