Maven工程通过pom.xml里的<dependency>来定义依赖项。当然,我们不会少定义依赖项,否则编译不通过。不过,如果我们多定义了依赖项虽然不会造成灾难,但可能会造成一些问题,比如:

  • 多余的依赖项造成阅读和理解的困难。
  • Spring的@ComponentScan将扫描出多余的组件。特别地,如果这些组件还需要配置才能使用则造成一些意想不到的问题,并且发现和纠正这些问题也比较困难。
  • 如果多余的依赖项为compile或runtime作用域,则其它依赖本工程的工程也将依赖这个多余的工程。如果运行时出了问题则更难处理。

因此,我们希望在pom.xml里定义的依赖项不多不少,并且其作用域(<scope>)也恰到好处,刚好满足本工程的需要。问题是该如何进行检查呢?答案是使用maven-dependency-plug插件。

maven-dependency-plugin插件简介

maven-dependency-plugin用于管理依赖项,提供了多个可执行的goal,在此我们只关心其中的analyze和tree两个。

下面是dependency:analyze的一个输出样例片断:

>mvn dependency:analyze

[INFO] --- maven-dependency-plugin:2.8:analyze (default-cli) @ wtp-core ---
[WARNING] Used undeclared dependencies found:
[WARNING] org.springframework:spring-beans:jar:3.2..RELEASE:compile
[WARNING] Unused declared dependencies found:
[WARNING] junit:junit:jar:4.7:test
[WARNING] org.springframework:spring-test:jar:3.2..RELEASE:test
[WARNING] org.slf4j:jcl-over-slf4j:jar:1.6.:runtime
[WARNING] org.slf4j:slf4j-log4j12:jar:1.6.:runtime
[WARNING] commons-lang:commons-lang:jar:2.5:test

官方文档里说,dependency:analyze是通过分析bytecode来输出报告。上面的输出有两部分,一是Used undeclared dependencies(使用但未定义的依赖项),二是Unused declared dependencies(未使用但却定义的依赖项)。

  • Used undeclared dependencies
    该列表列出的是程序代码直接用到的、但并没在pom.xml里定义的依赖项。
  • Unused declared dependencies
    该列表列出的是程序代码没用到的、但在pom.xml里定义的依赖项。注意,该列表可能不准确,因为程序代码可能使用了反射技术,在运行时需要这些jar包存在。

再说说dependency:tree,下面是一个输出样例片断:

>mvn dependency:tree

com.wisetop.wtp:wtp-core:jar:2.2.-SNAPSHOT
+- junit:junit:jar:4.7:test
+- org.springframework:spring-test:jar:3.2..RELEASE:test
+- org.slf4j:slf4j-api:jar:1.6.:compile
+- org.slf4j:jcl-over-slf4j:jar:1.6.:runtime
+- org.slf4j:slf4j-log4j12:jar:1.6.:runtime
| \- log4j:log4j:jar:1.2.:runtime
+- org.springframework:spring-core:jar:3.2..RELEASE:compile
| \- commons-logging:commons-logging:jar:1.1.:provided
+- org.springframework:spring-context:jar:3.2..RELEASE:compile
| +- org.springframework:spring-aop:jar:3.2..RELEASE:compile
| +- org.springframework:spring-beans:jar:3.2..RELEASE:compile
| \- org.springframework:spring-expression:jar:3.2..RELEASE:compile
+- org.springframework:spring-tx:jar:3.2..RELEASE:compile
| \- aopalliance:aopalliance:jar:1.0:compile
+- org.springframework:spring-context-support:jar:3.2..RELEASE:compile
+- commons-beanutils:commons-beanutils:jar:1.8.:compile
+- dom4j:dom4j:jar:1.6.:compile
| \- xml-apis:xml-apis:jar:1.0.b2:compile
+- com.wisetop.extra.oracle:wt-oracle-xdb6:jar:11.2.0.3:compile
| \- com.wisetop.extra.oracle:wt-oracle-ojdbc6:jar:11.2.0.3:compile
+- commons-lang:commons-lang:jar:2.5:test
+- org.quartz-scheduler:quartz:jar:1.7.:compile
+- commons-codec:commons-codec:jar:1.4:compile
\- javax.servlet:javax.servlet-api:jar:3.0.:provided

该命令列出了各依赖项、以及传递出来的依赖项。通过此命令我们可以识别出依赖项来自何处,特别地,如果你只关心某个特定的依赖项,比如上面的log4j是怎么出来的,你可加上过滤条件:

>mvn dependency:tree -Dincludes=log4j:log4j

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ wtp-core ---
[INFO] com.wisetop.wtp:wtp-core:jar:2.2.-SNAPSHOT
[INFO] \- org.slf4j:slf4j-log4j12:jar:1.6.:runtime
[INFO] \- log4j:log4j:jar:1.2.:runtime

当然,你可以简写为:

>mvn dependency:tree -Dincludes=*:log4j

其输出是一样的。

另外,一个依赖项可能来自多处,你可加上"-Dverbose"参数进行查看。例如,下面的命令将显示spring-core可来自多处:

>mvn dependency:tree -Dincludes=*:spring-core -Dverbose

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ wtp-core ---
[INFO] com.wisetop.wtp:wtp-core:jar:2.2.-SNAPSHOT
[INFO] +- org.springframework:spring-test:jar:3.2..RELEASE:test
[INFO] | \- (org.springframework:spring-core:jar:3.2..RELEASE:test - omitted for duplicate)
[INFO] +- org.springframework:spring-core:jar:3.2..RELEASE:compile
[INFO] +- org.springframework:spring-context:jar:3.2..RELEASE:compile
[INFO] | +- org.springframework:spring-aop:jar:3.2..RELEASE:compile
[INFO] | | \- (org.springframework:spring-core:jar:3.2..RELEASE:compile - omitted for duplicate)
[INFO] | +- org.springframework:spring-beans:jar:3.2..RELEASE:compile
[INFO] | | \- (org.springframework:spring-core:jar:3.2..RELEASE:compile - omitted for duplicate)
[INFO] | +- (org.springframework:spring-core:jar:3.2..RELEASE:compile - omitted for duplicate)
[INFO] | \- org.springframework:spring-expression:jar:3.2..RELEASE:compile
[INFO] | \- (org.springframework:spring-core:jar:3.2..RELEASE:compile - omitted for duplicate)
[INFO] +- org.springframework:spring-tx:jar:3.2..RELEASE:compile
[INFO] | \- (org.springframework:spring-core:jar:3.2..RELEASE:compile - omitted for duplicate)
[INFO] \- org.springframework:spring-context-support:jar:3.2..RELEASE:compile
[INFO] \- (org.springframework:spring-core:jar:3.2..RELEASE:compile - omitted for duplicate)

该输出显示,我们在pom.xml里直接依赖了spring-core,并且告诉我们如果我们不直接依赖spring-core的话则它也可能来自何处。

关于更多使用方法请参见dependency:tree的说明文档,以及过滤依赖树

使用maven-dependency-plugin对pom.xml文件进行优化

上面说到,优化的目标是不多不少地定义依赖项。不仅依赖项的个数要不多不少,而且依赖项的作用域也要恰到好处。其验证方法是:

  1. 检查是否少定义了依赖项,即执行mvn clean dependency:analyze后,应
    • 没有Used undeclared dependencies列表。
    • Unused declared dependencies列表中的依赖项需人工判断是否有必要存在,若无必要则请删除。

      特别地,对于其中的compile和test依赖项,如果不需要或能被其它依赖项传递带出则请删除。检查方法是:先删除该依赖项重试。如果重试成功则保持删除,如果不成功则改为test再重试,如果重试不成功则改为compile。如果你熟悉dependency:tree命令,你也可以从该命令的输出直接看到预期的结果。

      既然我们的代码没用到那个依赖项,为什么删除该依赖项可能导致编译不通过呢?这可能的原因是我们所依赖的依赖项其本身又依赖了其它依赖项,但它们之间是provided或optional关系,导致不传递依赖。

  2. 检查是否多定义了依赖项,并检查作用域是否定大了,即执行
    mvn clean dependency:analyze -Dmaven.test.skip=true后,应
    • 没有Used undeclared dependencies列表。上面第一步通过后,这里肯定不会出现该列表。
    • Unused declared dependencies列表中可以含有任意作用域的依赖项。我们需要去检查能否缩小作用域的范围,特别地,需要重点检查compile作用域是否能缩小到test、provided、runtime和system,如果能则改之。
  3. 以上步骤中如果修改了pom.xml文件,则请回到第一步重新开始验证,以保证优化前能编译通过。
  4. 执行mvn clean test命令做进一步验证。

需要特别说明的是:

  • 上面第一步是检查main/java和test/java中所有的代码,并保证能编译通过、并且没缺少依赖项(即没有Used undeclared dependencies列表),但可能多定了依赖项。
  • 第二步比第一步多了“-Dmaven.test.skip=true”参数,使得只生成target/classes、不生成target/test-classes,其含义是仅检查main/java中的代码,其意图是,我们希望找到那些仅被main/java或test/java用到的依赖项,以此尽量缩小作用域、使得作用域定义得恰到好处。

    对于某个依赖项,

    • 如果被main/java用到,则不论是否被test/java用到,其作用域都以main/java为准。
    • 如果被test/java用到,并且被main/java间接用到,则作用域也以main/java为准。

      请考虑这样一个场景,假设有A、B两个依赖项,并且A依赖于B。如果main/java只用到A、没用到B,而test/java用到B,则需把B定义为与A一样的作用域。需要说明的是,

      • 如果test/java也没用到B,则根本就不需要定义B。Maven的传递依赖机制将会自动带出B。
      • 即使main/java没直接用到B,但编译时可能需要B存在。例如,A中的某个对象继承了B中的对象,并且我们当前又再次继承了A中的这个对象。
    • 如果仅被test/java用到,则作用域可定义为test。
    • 上面说的“以main/java为准”的含义是保持与Maven的依赖机制相同。
  • 上面命令都带上了clean,除非你清楚命令的含义否则不要去掉。特别地,maven-dependency-plugin所分析的不是源程序,而是编译后的bytecode。
  • 你可通过执行dependency:tree来查看依赖项出自何处,以及你在pom里定义的依赖项本身又传递出来了哪些其它依赖项。
  • 最后,如果你的系统是多模块工程,则最好从底向上逐个优化pom,并且,每当你完成优化了一个工程后不要忘记了执行mvn install,这是因为在你继续到下一个工程时,dependency:analyze只从Maven库中去找其所依赖的pom.xml文件。

如何优化pom依赖项?的更多相关文章

  1. maven删除不必要的依赖;优化pom依赖研究

    mvn dependency:copy-dependencies -DoutputDirectory=/home/admin/git/oceanus/test 会把所有依赖的插件版本都拷贝进去,而不是 ...

  2. maven: 打包可运行的jar包(java application)及依赖项处理

    IDE环境中,可以直接用exec-maven-plugin插件来运行java application,类似下面这样: <plugin> <groupId>org.codehau ...

  3. maven常用插件: 打包源码 / 跳过测试 / 单独打包依赖项

    一.指定编译文件的编码 maven-compile-plugin <plugin> <groupId>org.apache.maven.plugins</groupId& ...

  4. 重新加载maven项目的依赖项

    最近在调试reportNG,测试允许完以后,报告总是使用的testNG的格式,并且只有index和overview两个文件. 找了好多帖子,大家都是那么设置的都没有问题,难道是哥人品不好?错! 大家基 ...

  5. 导入时如何定制spring-boot依赖项的版本

    spring-boot通过maven的依赖管理为我们写好了很多依赖项及其版本,我们可拿来使用.spring-boot文档介绍了两种使用方法,一是继承,二是导入. 通过<parent>继承: ...

  6. java组件不存在解决方案:右侧Maven Projects展开后左上角第一个刷新按钮 刷新后就会从新加载所有java的依赖项了

    java组件不存在解决方案:右侧Maven Projects展开后左上角第一个刷新按钮 刷新后就会从新加载所有java的依赖项了 软件:idea 问题产生:其他同事进行开发,引入新java组件后提交 ...

  7. (30)导入时如何定制spring-boot依赖项的版本【转载】【从零开始学Spring Boot】

    此文章转载地址:http://www.tuicool.com/articles/RJJvMj3 请注重作者的版权. spring-boot通过maven的依赖管理为我们写好了很多依赖项及其版本,我们可 ...

  8. IntelliJ IDEA + Maven iml文件中依赖项的需求是什么?

    在Maven中,项目的依赖关系在pom.xml文件中指定.在IntelliJ IDEA中,即使对于Maven项目,相同的信息也存储在iml文件中.在两个地方有相同的信息需要什么? 当导入Maven项目 ...

  9. 什么是Maven? 使用Apache Maven构建和依赖项管理

    通过优锐课java架构学习中,学到了不少干货,整理分享给大家学习. 开始使用最流行的Java构建和依赖管理工具Maven Apache Maven是Java开发的基石,也是Java使用最广泛的构建管理 ...

随机推荐

  1. ant的入门 配置与安装

    最近需要用ant来生成文件,java类.我才开始了解了这个工具.仔细看了一下,感觉这个小工具的强大功能. 博主也是初学者,在网上收集了资料,尝试了配置:感觉有些高手写得不错变引用之. 配置如下: 以上 ...

  2. tr 替换删除字符

    1.关于tr    通过使用 tr,您可以非常容易地实现 sed 的许多最基本功能.您可以将 tr 看作为 sed 的(极其)简化的变体:它可以用一个字符来替换另一个字符,或者可以完全除去一些字符.您 ...

  3. 安全运维之:Linux系统账户和登录安全

    一.合理使用Shell历史命令记录功能 在Linux下可通过history命令查看用户所有的历史操作记录,同时shell命令操作记录默认保存在用户目录下 的.bash_history文件中,通过这个文 ...

  4. 棋盘覆盖(大数阶乘,大数相除 + java)

    棋盘覆盖 时间限制:3000 ms  |  内存限制:65535 KB 难度:3   描述 在一个2k×2k(1<=k<=100)的棋盘中恰有一方格被覆盖,如图1(k=2时),现用一缺角的 ...

  5. POJ 2479-Maximum sum(线性dp)

    Maximum sum Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 33918   Accepted: 10504 Des ...

  6. Swift继承的用法

    一个类可以继承另一个类的方法,属性和其它特性.当一个类继承其它类,继承类叫子类,被继承类叫超类(或父类).在Swift中,继承是区分「类」与其它类型的一个基本特征. 在Swift中,类可以调用和访问超 ...

  7. C#中大List的内存分配

    之前在开发中只用到List的时候几乎就是拿过来就用,从来没有考虑过List的内存分配问题,试想一个有10万元素的List的在构造和添加元素时内存是如何变化的呢?在MSDN上关于List的Capacit ...

  8. tostring的用法

    ToString()可空参数单独使用,同时可以加一个格式化参数,具体方式如下: . 取中文日期显示_年月 currentTime.ToString("y"); 格式:2007年1月 ...

  9. jdbc 日期 时间

    //pstmt.setDate(1, new Date(new java.util.Date().getTime())); pstmt.setTime(1, new Time(new java.uti ...

  10. data source 和initial catalog

    initial catalog与database的区别是什么Initial Catalog: DataBase: 两者没有任何区别只是名称不一样,就好像是人类的真实姓名与曾用名一样..都可以叫你. * ...