本文讨论可选依赖和排除依赖.  帮助用户理解它们是什么, 如何使用, 它们如何工作, 以及什么时候使用它们最合适. 本文也将解释为什么排除是基于单个依赖的, 而非POM级别的.

Optional Dependencies

可选依赖用在不能真正地将一个项目划分为多个子模块时.  一些依赖只在该项目中的某些特性中使用, 并且如果这些特性没有使用到的话, 这些依赖就不需要.  最理想的情况, 这样的特性会被划分到一个依赖于核心功能工程的子模块, 这个新子模块将只有非可选依赖, 因为一旦你决定使用该子模块的功能, 你就会需要这些依赖.

然而, 如果该项目无法划分, 这些依赖就需要被声明为可选的. 如果一个用户想要使用和一个可选依赖相关的功能, 他们需要在自己的工程里声明那个可选依赖.  这并不是处理这种情况的最清晰的方式.. 但可选依赖和依赖排除都是 stop-gap 方案.

Why use optional dependencies?

声明可选依赖不仅仅在节省内存空间问题上很重要. 当一个用户使用一个工程时, 控制实际需要的依赖列表非常重要. 因为这些jar包可能会最终放入一个WAR, EAR, EJB等等. 包含了错误的jar包, 可能会违反许可协议, 引起路径问题等等.

How do I use the optional tag?

声明一个可选依赖, 只需要在你的依赖声明中简单地设置 <optional> 标签为 true. 如下例所示:

optional类的isEmpty和isPresent optional dependency_maven

How do optional dependencies work?

Project-A -> Project-B

如果 project A 依赖于 project B, 当A将B声明为可选依赖时, 这个关系就确定下来了. 这就像一个正常的构建, B将会被添加到它的类路径里.

Project-X -> Project-A

但当另一个工程 project X 将工程A声明为它的依赖时, 可选依赖就发挥效用了. 你会发现, 工程B没有被包含到X的类路径中. 你需要在POM中直接声明B, 才能将它包含到X的类路径中.

optional类的isEmpty和isPresent optional dependency_maven项目_02

Example

假设有一个工程名为X2, 它和Hibernate有相似的功能,  支持很多种数据库驱动, 比如mysql, postge, oracle等. 构建X2需要所有这些依赖, 但你的项目未必. 所以, 将这些依赖声明为可选的对X2来说是非常实用的, 这样无论何时你的项目声明了X2作为一个直接依赖, X2支持的所有驱动就不会自动地包含到你的工程的类路径里, 你需要直接声明你需要的数据库驱动.

[注意] 在理想情况下, 不应用使用可选依赖. 在面向对象设计中, 有个单一职责性原则, 意指一个类应该只有一项职责, 而不是糅合太多功能. 这个原则在规划maven项目时也同样适用. 上例中, 更好的做法是为mysql和postgreSQL分别创建一个maven项目, 基于同样的groupId分配不同的artifactId. 在各自的pom中声明对应的jdbc驱动依赖, 不使用可选依赖. 用户根据需要选择使用. 由于传递性依赖的作用, 就不用再声明jdbc驱动依赖了.

Dependency Exclusions

由于maven 2.x的依赖传递规则, 很有可能你的项目类路径中包含了一些不想要的依赖. 比如说, 你的项目依赖的那些工程可能没有正确地声明他们的依赖集合. 为了应对这个特殊的场景, maven2.x提出了依赖排除的概念. 排除是针对POM中的一个特定的依赖设置的, 以一个特定的groupId和artifactId为标识. 如果你声明了排除依赖, 当你构建你的工程时, 该构件就不会被添加到你的工程的类路径中.

How to use dependency exclusions

我们在pom的<dependency>部分添加<exclusions>标签.

optional类的isEmpty和isPresent optional dependency_maven_03

How dependency exclusion works and when to use it ( as a last resort! )

如下图所示. 工程A同时依赖于B,C. 而工程B又依赖于D. 默认情况下, A的类路径会包含: B, C, D, E, F.

如果我们不需要使用工程D, 同时不想把它的依赖添加到A的类路径中, 因为我们已知D的某些依赖, 比如说E, 在仓库中找不到了. 而你又不需要工程B中依赖于工程D的那部分功能. 在这种情况下, B的开发者可能会提供一个关于D的可选依赖.

然而!!! 他们没有提供!!!  作为最后的手段, 你依然可以选择在你这一侧把不想要的依赖排除掉.

optional类的isEmpty和isPresent optional dependency_maven项目_04

optional类的isEmpty和isPresent optional dependency_ci_05

如果我们将工程A发布到一个仓库, 并且工程X声明一个关于A的普通依赖, 那么D还会被排除在类路径之外吗?

答案是会. 工程A已经声明了它不需要工程D就可以运行, 所以这会产生一个传递的依赖.

现在, 考虑工程X依赖于Y. 工程Y也有一个关于B的依赖, 且它确实需要由工程D支持的特性. 因此, 在它的依赖列表里不会出现排除D的情况. 它也可能提供一个额外的仓库, 在那里我们可以处理工程E. 在这种情况下, 工程D不是全局排除就非常重要了, 因为它是Y的一个合法依赖.

Why exclusions are made on a per-dependency basis, rather than at the POM level

主要是为了确保依赖关系图是可预知的, 避免排除了不应排除的依赖. 如果你需要排除, 你应绝对确定你引入的哪个依赖带来了不必要的依赖传递.