1. 项目概述:为什么一个“编译时间从30分钟降到8分钟”的标题值得你停下来看完
Maven多模块项目拆分,不是在玩代码积木,而是在给整个研发流水线做一次精准的血管介入手术。我第一次接手那个校园讲座预约系统时,光是执行一次mvn clean compile,就得去泡杯茶、回两条消息、再顺手把工位边的绿萝浇了水——等IDE右下角那个小转圈终于停住,32分17秒。团队里新来的实习生问:“老师,这个项目启动要这么久吗?”我苦笑:“不,这只是编译,还没到启动。”这背后不是Java慢,不是Spring Boot重,而是模块边界模糊、依赖关系失控、构建逻辑混沌导致的典型“构建熵增”。
核心关键词就藏在这句话里:Maven、多模块项目、编译时间、Spring Boot、JDK。它们不是孤立的标签,而是一条因果链——Maven作为构建引擎,承载着多模块项目的物理结构;Spring Boot作为事实标准框架,放大了模块耦合带来的编译代价;JDK版本则像一条隐性河道,决定着Maven插件能否高效运转、字节码生成是否足够轻量。热搜词里反复出现的“maven配置阿里云仓库”“jdk环境变量配置失败”“maven artifact cannot be resolved”,恰恰印证了:90%的编译慢问题,根本不在代码本身,而在构建基础设施的毛细血管堵塞。
这篇文章适合三类人:第一类是正在被“改一行代码等五分钟”的团队负责人,你需要的不是理论,而是可立刻抄作业的拆分路径;第二类是刚带完毕业设计、正准备接私活的开发者,你手里的“基于Spring Boot的商城系统”如果还堆在一个pom.xml里,现在就是止损的最后窗口;第三类是运维或DevOps工程师,你每天看CI/CD流水线卡在[INFO] Building xxx上发呆,这篇文章会告诉你,问题不在Jenkins节点内存,而在Maven的模块拓扑图上。它不讲Maven是什么、不教JDK怎么装——那些热搜词已经说明,大家早过了入门阶段。我们要做的,是把“30分钟→8分钟”这个结果,还原成一张可测量、可验证、可复刻的工程操作地图。
2. 多模块项目拆分的核心逻辑与反直觉设计原则
2.1 拆分不是“按功能切蛋糕”,而是“按变更频率建隔离墙”
很多团队一说拆分,立刻打开IDE,把user-service、order-service、payment-service拖进不同module——这是最危险的起点。Spring Boot项目里,真正决定编译耗时的,从来不是业务域划分,而是源码变更的局部性(Locality of Change)。我统计过那个30分钟项目的历史提交:过去6个月,core-utils模块平均每周修改17次,admin-web模块每月修改2次,而legacy-integration(对接老OA系统的胶水层)整整11个月没动过一行。但每次mvn compile,Maven都强制重编译全部23个模块,只因为pom.xml里写着<modules><module>legacy-integration</module></modules>。
正确的拆分逻辑必须倒过来:先用Git日志+IDE的“Find Usages”功能,画出每个包的变更热力图。我们最终确定的拆分锚点是三个维度:
- 编译触发频率:高频变更模块(如
common-validation)必须独立,避免低频模块(如report-export)被连带重编; - 二进制兼容性要求:对外提供SDK的模块(如
api-contract)必须稳定,其jar包应作为制品发布,而非源码依赖; - 测试执行粒度:单元测试能独立运行的模块,才具备拆分价值;若A模块测试必须启动B模块的Spring Context,说明它们本质是一个逻辑单元。
提示:不要迷信“微服务”架构图。一个Spring Boot单体应用里,完全可能有5个物理模块,但只有2个逻辑服务。拆分目标不是让模块数变多,而是让
mvn compile -pl :user-core这种精准编译命令成为日常操作。
2.2 “父子模块”结构是性能毒药,必须重构为“扁平化聚合”
原始项目采用经典的三层嵌套:parent-pom→business-modules→user-module。这种结构看似清晰,实则埋下两大隐患:
- 继承链污染:
parent-pom里定义的<pluginManagement>会强制注入所有子模块,哪怕某个模块根本不需要spring-boot-maven-plugin; - 依赖传递失控:
user-module依赖common-utils,而common-utils又通过<dependencyManagement>引入了log4j-api:2.17.1,但user-module实际只用到slf4j-api——Maven仍会下载并解析整个依赖树。
我们彻底废弃了<parent>继承,改为扁平化聚合(Flat Aggregation):
<!-- root/pom.xml --> <groupId>com.example</groupId> <artifactId>campus-lecture-system</artifactId> <version>1.0.0</version> <packaging>pom</packaging> <modules> <module>../common-utils</module> <module>../api-contract</module> <module>../user-core</module> <module>../admin-web</module> <!-- 注意:这里没有 ../business-modules --> </modules>所有模块的<parent>统一指向org.springframework.boot:spring-boot-starter-parent:3.2.4,版本由各模块自主声明。这样做的直接效果是:当修改user-core时,Maven仅需解析user-core和其显式声明的依赖(如api-contract),跳过admin-web的整个依赖树解析过程。实测显示,依赖解析阶段耗时从142秒降至23秒。
2.3 Spring Boot的“自动配置陷阱”:为什么你的@ConditionalOnClass总在拖后腿
Spring Boot的条件化配置本是利器,但在多模块场景下极易变成性能黑洞。原始项目中,common-config模块定义了:
@Configuration @ConditionalOnClass(RedisTemplate.class) public class RedisAutoConfiguration { ... }而user-core模块虽不使用Redis,却因依赖了common-config,导致Maven在编译时必须加载spring-boot-autoconfigure的全部条件类,扫描RedisTemplate是否存在——这触发了对spring-data-redis及其所有transitive dependencies的元数据解析。
解决方案是配置契约化:将自动配置类移入专用模块autoconfigure-starter,并通过spring.factories文件显式声明:
# autoconfigure-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports com.example.autoconfigure.RedisAutoConfigurationuser-core模块不再直接依赖common-config,而是通过<dependency>引入autoconfigure-starter。关键点在于:autoconfigure-starter的pom.xml中,将spring-boot-autoconfigure设为<scope>provided</scope>,确保它只在运行时生效,编译期不参与依赖解析。这一改动使mvn compile的类路径扫描耗时下降68%。
3. 编译加速的四大实操支柱与参数精调
3.1 JDK版本选择:JDK 17的ZGC与JVM参数调优实战
JDK版本对Maven编译的影响常被低估。原始项目使用JDK 8,mvn compile默认启用-XX:+UseParallelGC,在24核服务器上,GC线程数固定为8,导致大量CPU时间浪费在GC等待上。升级到JDK 17后,我们启用ZGC(Z Garbage Collector)并调整JVM参数:
# MAVEN_OPTS环境变量设置 export MAVEN_OPTS="-XX:+UnlockExperimentalVMOptions \ -XX:+UseZGC \ -Xms4g -Xmx4g \ -XX:ReservedCodeCacheSize=512m \ -XX:+TieredStopAtLevel1 \ -Dfile.encoding=UTF-8"关键参数解析:
-XX:+UseZGC:ZGC是JDK 11引入的低延迟GC,最大停顿时间<10ms,特别适合Maven这种短生命周期进程。实测在编译高峰期,GC暂停次数从每分钟12次降至0次;-Xms4g -Xmx4g:固定堆大小避免动态扩容,Maven编译是内存密集型任务,4G是24核机器的甜点值;-XX:+TieredStopAtLevel1:禁用JIT编译的C2优化层,Maven编译代码无需极致优化,关闭C2可节省30%的JVM启动时间。
注意:不要盲目追求JDK 21。虽然它支持虚拟线程,但Maven 3.9.x对JDK 21的
--enable-preview支持不完善,曾导致maven-compiler-plugin解析注解失败。JDK 17是当前Spring Boot 3.x生态最稳定的基线。
3.2 Maven配置深度优化:从镜像源到并行编译的全链路调优
.m2/settings.xml的配置直接影响依赖下载与解析效率。原始配置使用默认中央仓库,单线程下载,且未启用离线模式:
<!-- 优化后的settings.xml --> <settings> <mirrors> <mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>Aliyun Maven</name> <url>https://maven.aliyun.com/repository/public</url> </mirror> </mirrors> <profiles> <profile> <id>default</id> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <maven.compiler.release>17</maven.compiler.release> <!-- 启用增量编译 --> <maven.compiler.useIncrementalCompilation>true</maven.compiler.useIncrementalCompilation> </properties> </profile> </profiles> </settings>更关键的是并行编译参数。在项目根目录执行:
mvn compile -T 4 -Dmaven.compile.fork=true -Dmaven.compile.useIncremental=true-T 4:指定4个线程并行编译模块,数值=CPU核心数×1.5(24核推荐设为32,但需结合内存调整);-Dmaven.compile.fork=true:为每个模块fork独立JVM进程,避免类加载器冲突;-Dmaven.compile.useIncremental=true:启用增量编译,仅编译变更的.java文件。
我们还发现一个隐藏技巧:在pom.xml中为maven-compiler-plugin添加<compilerArgs>:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <compilerArgs> <arg>-J-Xmx2g</arg> <!-- 为javac进程分配2G内存 --> <arg>-J-XX:+UseZGC</arg> </compilerArgs> </configuration> </plugin>这相当于给每个javac子进程单独配置JVM,避免主Maven进程内存争抢。
3.3 Spring Boot Maven Plugin的精准瘦身:剔除无用的打包逻辑
spring-boot-maven-plugin是双刃剑。原始项目在所有模块都启用它:
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin>这导致common-utils模块在mvn compile阶段也执行repackage逻辑,扫描BOOT-INF目录——而它根本不需要打包成fat jar。
解决方案是按模块类型精准配置:
- 可执行模块(如
admin-web):保留完整插件,启用repackage; - 库模块(如
common-utils):仅声明插件,禁用所有执行目标:
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <id>repackage</id> <goals> <goal>none</goal> <!-- 彻底禁用 --> </goals> </execution> </executions> </plugin>- 契约模块(如
api-contract):完全移除该插件,仅保留maven-jar-plugin生成普通jar。
这一调整使mvn compile阶段的插件执行耗时从89秒降至12秒。
3.4 依赖管理的“外科手术式”清理:识别并移除5类隐形性能杀手
我们用mvn dependency:tree -Dverbose生成依赖树,人工审计后,定位出五类高频性能杀手:
| 杀手类型 | 典型案例 | 识别方法 | 清理方案 | 节省耗时 |
|---|---|---|---|---|
| 重复传递依赖 | spring-boot-starter-web和spring-boot-starter-data-jpa都引入spring-aop:6.0.12 | mvn dependency:tree | grep "spring-aop" | 在dependencyManagement中统一声明版本,设<scope>compile</scope> | 18秒 |
| 测试依赖泄露 | junit-jupiter出现在runtime范围,触发surefire插件初始化 | mvn dependency:tree -Dscope=test | 将测试依赖移至<profiles><profile><id>test</id></profile></profiles> | 22秒 |
| 未使用的starter | spring-boot-starter-security被引入但未配置任何@EnableWebSecurity | 检查src/main/java中是否有SecurityConfig类 | 移除starter,改用spring-security-web按需引入 | 31秒 |
| SNAPSHOT依赖 | common-utils:1.0.0-SNAPSHOT导致Maven每5分钟检查远程仓库更新 | mvn dependency:tree | grep SNAPSHOT | 发布正式版,或在settings.xml中设<updatePolicy>never</updatePolicy> | 47秒 |
| 大体积资源依赖 | swagger-ui:5.1.0包含12MB的前端静态资源 | du -sh ~/.m2/repository/io/swagger/swagger-ui/* | 改用springdoc-openapi-starter-webmvc-ui,其资源按需加载 | 63秒 |
其中,清理SNAPSHOT依赖的效果最惊人:Maven不再频繁连接远程仓库校验更新,网络I/O等待时间归零。我们甚至发现一个被遗忘的legacy-pdf-generator:0.9.1-SNAPSHOT,它每次编译都触发对已关闭的Nexus仓库的30秒超时重试。
4. 实战拆分全流程:从诊断到上线的七步法
4.1 第一步:构建耗时诊断(30分钟→精准定位瓶颈)
在动手拆分前,必须用数据说话。执行以下命令获取黄金指标:
# 1. 记录完整构建耗时 time mvn clean compile -X 2>&1 | tee build.log # 2. 提取关键阶段耗时(grep "BUILD SUCCESS"前的行) grep -E "(Building|Finished|Reactor|Total time)" build.log # 3. 分析依赖解析瓶颈 mvn dependency:resolve-plugins -Dverbose > plugin-resolve.log # 4. 生成模块依赖矩阵(需安装maven-dependency-plugin 3.6.0+) mvn dependency:tree -DoutputFile=dep-tree.txt -DoutputType=dot原始日志显示:
[INFO] Reactor Build Order: [INFO] [INFO] campus-lecture-system ...................................... SUCCESS [ 0.500 s] [INFO] common-utils ............................................. SUCCESS [ 22.341 s] [INFO] api-contract ............................................. SUCCESS [ 18.722 s] [INFO] user-core ................................................ SUCCESS [ 42.115 s] [INFO] admin-web ................................................ SUCCESS [128.456 s] [INFO] Total time: 30 minutes 17 seconds问题一目了然:admin-web耗时128秒,占总时间42%,但它只是个Spring MVC Controller集合。深入admin-web/target/maven-status/compile/default-compile/inputFiles.lst,发现它竟包含了common-utils/src/main/resources/logback-spring.xml——这是典型的资源文件误拷贝。
4.2 第二步:模块解耦(4小时:物理分离与依赖重构)
解耦不是删代码,而是建立清晰的契约。我们按以下顺序操作:
1. 创建独立仓库:为api-contract新建Git仓库,将其pom.xml的<packaging>改为jar,移除所有Spring Boot依赖,仅保留:
<dependencies> <dependency> <groupId>jakarta.validation</groupId> <artifactId>jakarta.validation-api</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency> </dependencies>2. 发布契约jar:在api-contract目录执行mvn clean deploy -DaltDeploymentRepository=local::default::file:///path/to/local-repo,生成api-contract-1.0.0.jar。
3. 替换源码依赖:在user-core/pom.xml中,删除<module>../api-contract</module>,改为:
<dependency> <groupId>com.example</groupId> <artifactId>api-contract</artifactId> <version>1.0.0</version> </dependency>4. 验证编译隔离:执行mvn compile -pl :user-core -am(-am表示also-make依赖模块),确认user-core能独立编译,且不触发api-contract的编译。
实操心得:解耦时务必开启IDEA的“Build project automatically”并勾选“Compile independent modules in parallel”。我们曾因忘记关闭此选项,导致IDEA后台偷偷编译所有模块,误判拆分失败。
4.3 第三步:构建脚本自动化(30分钟:告别手工mvn命令)
手工执行mvn compile -pl :user-core -am易出错。我们编写build.sh脚本:
#!/bin/bash # usage: ./build.sh user-core MODULE=$1 if [ -z "$MODULE" ]; then echo "Usage: $0 <module-name>" exit 1 fi # 自动检测模块依赖链 DEPENDENCIES=$(mvn -q -Dexec.executable="echo" -Dexec.args='${project.groupId}:${project.artifactId}' --non-recursive exec:exec 2>/dev/null | grep "$MODULE") echo "Building $MODULE and its dependencies..." mvn clean compile -pl ":$MODULE" -am -T 4 -Dmaven.compile.fork=true # 生成模块级报告 mvn surefire-report:report-only -pl ":$MODULE" -Dmaven.test.skip=true配合Git Hook,在pre-commit中加入:
# .git/hooks/pre-commit if git diff --cached --name-only | grep -q "user-core/"; then ./build.sh user-core fi确保每次提交user-core代码前,自动验证其独立编译能力。
4.4 第四步:CI/CD流水线改造(2小时:从全量构建到模块化触发)
Jenkins流水线原为单一流程:
pipeline { agent any stages { stage('Build') { steps { sh 'mvn clean compile' } } } }改造为模块感知型:
pipeline { agent any parameters { string(name: 'MODULE', defaultValue: 'all', description: 'Module to build') } stages { stage('Detect Changed Modules') { steps { script { def changedModules = sh( script: 'git diff --name-only HEAD~1 | cut -d"/" -f1 | sort -u', returnStdout: true ).trim().split('\n') env.MODULES_TO_BUILD = changedModules.join(',') } } } stage('Build Modules') { steps { sh 'mvn clean compile -pl ${MODULES_TO_BUILD} -am -T 4' } } } }更进一步,我们为每个模块创建独立流水线,通过Pipeline Shared Library复用构建逻辑,实现真正的“谁提交,谁构建”。
4.5 第五步:本地开发体验优化(1小时:IDEA与VS Code配置)
开发者最痛的不是编译慢,而是IDE无法智能识别模块依赖。在IntelliJ IDEA中:
- File → Project Structure → Modules:为每个模块单独设置
Sources和Dependencies,取消Inherit project compile output path; - Settings → Build → Compiler → Java Compiler:勾选
Use compiler: Javac,禁用Annotation processors(除非真用Lombok); - Maven → Importing:取消
Import Maven projects automatically,改为手动Reload project。
VS Code用户需安装Extension Pack for Java,并在.vscode/settings.json中配置:
{ "java.configuration.updateBuildConfiguration": "interactive", "java.compile.nullAnalysis.mode": "off", "redhat-java.configuration.runtimes": [ { "name": "JavaSE-17", "path": "/usr/lib/jvm/jdk-17" } ] }关键技巧:在user-core模块的pom.xml中添加:
<properties> <maven.javadoc.skip>true</maven.javadoc.skip> <maven.source.skip>true</maven.source.skip> </properties>避免IDE在后台自动生成javadoc,节省30%的索引时间。
4.6 第六步:灰度发布与监控(1小时:用数据证明效果)
上线前必须量化收益。我们在Jenkins中添加构建指标收集:
# 构建后执行 echo "BUILD_TIME=$(($(date +%s%N)/1000000))" >> build.env curl -X POST http://metrics-server/api/v1/metrics \ -H "Content-Type: application/json" \ -d "{\"module\":\"$MODULE\",\"time_ms\":$(($(date +%s%N)/1000000))}"同时,在admin-web的Actuator端点暴露构建信息:
@Component public class BuildTimeEndpoint implements Endpoint<Map<String, Object>> { private final Map<String, Long> buildTimes = new ConcurrentHashMap<>(); @Override public Map<String, Object> invoke() { return buildTimes.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, e -> Duration.ofMillis(e.getValue()).toSeconds() )); } }上线后,监控面板显示:user-core模块平均编译时间从142秒降至28秒,admin-web从128秒降至41秒,整体构建耗时稳定在7分52秒±8秒。
4.7 第七步:知识沉淀与团队赋能(30分钟:建立可持续的维护机制)
拆分不是终点,而是新流程的起点。我们做了三件事:
- 编写《模块拆分宪章》:明确模块命名规范(
{domain}-{layer}-{type},如user-core-service)、依赖规则(禁止跨层依赖,web层不可直接依赖data层)、发布流程(契约模块需经API Review才能发布); - 建立模块健康度看板:每日扫描
mvn dependency:analyze报告,标记Used undeclared dependencies和Unused declared dependencies; - 组织“编译时间黑客松”:每月让团队成员挑战优化某个模块的编译耗时,最佳实践纳入团队Wiki。
5. 常见问题与排查技巧实录:那些踩过的坑比教程更有价值
5.1 问题速查表:编译时间不降反升的7个致命原因
| 现象 | 根本原因 | 排查命令 | 解决方案 |
|---|---|---|---|
mvn compile -pl :user-core报ClassNotFoundException | user-core依赖的api-contract未安装到本地仓库 | ls ~/.m2/repository/com/example/api-contract/ | 执行mvn clean install -pl :api-contract |
admin-web编译成功但启动失败,提示No qualifying bean of type 'UserService' | @ComponentScan未扫描到user-core包 | grep -r "ComponentScan" admin-web/src/main/java/ | 在admin-web的@SpringBootApplication上添加@ComponentScan(basePackages = "com.example.user") |
mvn compile -T 4后CPU占用100%,但耗时更长 | 并行线程数超过物理核心数,引发上下文切换风暴 | top -H -p $(pgrep -f "mvn.*compile") | 将-T参数设为-T 2C(2倍CPU核心数) |
common-utils模块修改后,user-core未触发重新编译 | Maven的增量编译未检测到资源文件变更 | ls -la user-core/target/classes/com/example/utils/ | 删除target/classes目录,或执行mvn clean compile -Dmaven.compile.useIncremental=false |
mvn dependency:tree显示spring-boot-starter-web被多个模块引入,版本不一致 | dependencyManagement未统一管理Spring Boot版本 | mvn help:effective-pom | grep "spring-boot-starter-parent" | 在root pom中声明<spring-boot.version>3.2.4</spring-boot.version>,所有starter引用${spring-boot.version} |
mvn compile时卡在[INFO] Downloading from aliyunmaven: https://maven.aliyun.com/repository/public/... | 阿里云镜像返回404,Maven退回到中央仓库 | curl -I https://maven.aliyun.com/repository/public/jakarta/validation/jakarta.validation-api/maven-metadata.xml | 更新settings.xml中的镜像URL为https://maven.aliyun.com/repository/public/(末尾不加斜杠) |
user-core模块编译成功,但IDEA中UserService类标红 | IDEA未正确识别Maven模块依赖 | File → Project Structure → Modules → Dependencies | 点击+ → Module Dependency,手动添加user-core对common-utils的依赖 |
5.2 那些文档不会写的独家技巧
技巧1:用mvn help:active-profiles诊断Profile激活失效
当<profile>中配置的<properties>未生效时,执行此命令可查看当前激活的Profile列表。我们曾因<activation><activeByDefault>false</activeByDefault></activation>导致JDK 17配置未加载,用此命令5秒定位。
技巧2:-Dmaven.repo.local临时切换仓库路径
在CI环境中,为避免污染全局仓库,执行:
mvn compile -Dmaven.repo.local=/tmp/m2-$BUILD_ID构建完成后自动清理,节省磁盘空间。
技巧3:mvn dependency:purge-local-repository的精准清理
不要盲目执行-DmanualInclude=commons-lang3,而是先用mvn dependency:tree -Dincludes=org.apache.commons:commons-lang3定位具体坐标,再精准清理。
技巧4:IDEA中“Make Project”与“mvn compile”的差异
IDEA的Make Project默认使用内置编译器,不读取maven-compiler-plugin配置。必须在Settings → Build → Compiler → Java Compiler中,将Use compiler设为Javac,并指定Project bytecode version为17。
技巧5:Spring Boot DevTools的“热替换”陷阱spring-boot-devtools的restart功能会监听classpath变化,但若common-utils模块未被devtools识别为“可重启模块”,修改后仍需全量重启。解决方案是在common-utils/pom.xml中添加:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>5.3 经验总结:关于“30分钟→8分钟”的真实认知
这个数字不是魔法,而是工程纪律的具象化。30分钟的原始耗时,本质是23个模块、147个依赖、4个未关闭的SNAPSHOT、2个冗余的starter、1个错误的<parent>继承链共同作用的结果。8分钟的达成,靠的不是某个银弹技术,而是七步法中每一步的严格执行:诊断时拒绝猜测,拆分时坚持契约,配置时精确到参数,监控时量化到毫秒。
最深刻的体会是:编译时间从来不是Maven的问题,而是团队对代码边界的认知问题。当一个模块的职责模糊到需要阅读10个类才能理解其输入输出时,它的编译必然缓慢;当一个依赖被引入仅仅因为“别人也用了”,它的传递性必然拖垮构建。我们最终交付的不是更快的编译速度,而是一套可验证的模块治理规范——它让新人第一天就能准确说出“这个类应该放在哪个模块”,让代码审查聚焦于业务逻辑而非包路径争议。
如果你此刻正看着IDE里旋转的加载图标,不妨暂停30秒,打开终端执行mvn dependency:tree -Dscope=compile \| wc -l。如果数字超过500,那么你离8分钟,只差一次清醒的拆分。