在软件开发过程中,程序员会通过重构等方式不断地优化自己的代码,使其变得更简洁、更灵活。同理,程序员也应该能够对Maven项目的依赖了然于胸,并对其进行优化,如去除多余的依赖,显式地声明某些必要的依赖。
通过阅读本章前面的内容,读者应该能够了解到:Maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调节,以确保任何一个构件只有唯一的版本在依赖中存在。在这些工作之后,最后得到的那些依赖被称为已解析依赖(Resolved Dependency)。可以运行如下的命令查看当前项目的已解析依赖:
在account-email项目中执行该命令,结果如图5-5所示。
图5-5 已解析依赖列表
图5-5显示了所有account-email的已解析依赖,同时,每个依赖的范围也得以明确标示。
在此基础上,还能进一步了解已解析依赖的信息。将直接在当前项目POM声明的依赖定义为顶层依赖,而这些顶层依赖的依赖则定义为第二层依赖,以此类推,有第三、第四层依赖。当这些依赖经Maven解析后,就会构成一个依赖树,通过这棵依赖树就能很清楚地看到某个依赖是通过哪条传递路径引入的。可以运行如下命令查看当前项目的依赖树:
在acccount-email中执行该命令,效果如图5-6所示。
图5-6 已解析依赖树
从图5-6中能够看到,虽然我们没有声明org.slf4j:slf4j-api:1.3这一依赖,但它还是经过com.icegreen:greenmail:1.3成为了当前项目的传递性依赖,而且其范围是test。
使用dependency:list和dependency:tree可以帮助我们详细了解项目中所有依赖的具体信息,在此基础上,还有dependency:analyze工具可以帮助分析当前项目的依赖。
为了说明该工具的用途,先将5.3.1节中的spring-context这一依赖删除,然后构建项目,你会发现编译、测试和打包都不会有任何问题。通过分析依赖树,可以看到spring-context是spring-context-support的依赖,因此会得以传递到项目的classspath中。现在再运行如下命令:
结果如图5-7所示。
图5-7 使用但未声明的依赖与声明但未使用的依赖
该结果中重要的是两个部分。首先是Used undeclared dependencies,意指项目中使用到的,但是没有显式声明的依赖,这里是spring-context。这种依赖意味着潜在的风险,当前项目直接在使用它们,例如有很多相关的Java import声明,而这种依赖是通过直接依赖传递进来的,当升级直接依赖的时候,相关传递性依赖的版本也可能发生变化,这种变化不易察觉,但是有可能导致当前项目出错。例如由于接口的改变,当前项目中的相关代码无法编译。这种隐藏的、潜在的威胁一旦出现,就往往需要耗费大量的时间来查明真相。因此,显式声明任何项目中直接用到的依赖。
结果中还有一个重要的部分是Unused declared dependencies,意指项目中未使用的,但显式声明的依赖,这里有spring-core和spring-beans。需要注意的是,对于这样一类依赖,我们不应该简单地直接删除其声明,而是应该仔细分析。由于dependency:analyze只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖它就发现不了。很显然,该例中的spring-core和spring-beans是运行Spring Framework项目必要的类库,因此不应该删除依赖声明。当然,有时候确实能通过该信息找到一些没用的依赖,但一定要小心测试。