Java 工程中通常使用 maven(当然也有很多人使用 gradle)来管理项目依赖。maven 这样的构建工具极大的提升了工程的构建效率,我们只需要把相关依赖添加至配置文件即可,完全不用关心构建的过程。

在以前的文章中​​maven 中 dependencies 与 dependencyManagement 的区别​​介绍过关于 dependency 相关的用法,我们知道可以通过 dependency 将依赖添加至 pom.xml 文件中。不过在阅读其他项目的代码,尤其是一些组件端的代码的时候,会发现类似于下面这样的用法:

<dependency>
<groupId>some.company</groupId>
<artifactId>project-c</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>some.company</groupId>
<artifactId>project-d</artifactId>
<scope>provided</scope>
</dependency>

可能有的人知道 optional 与 provided 的作用,他们很快会说出这样是为了简化依赖,避免引入不必要的依赖项。可是二者有什么区别呢?本文就针对这个问题简单的回答下。

optional

关于 optional,最常见的例子就是数据库驱动。例如我们想做一个数据库组件,而且这个数据库组件需要支持多个数据库的话,例如 MySQL、PostgreSQL、Oracle,那么组件端编写的时候将各个数据库的驱动全部引入,像下面这样:

<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- oracle -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc14</artifactId>
</dependency>

<!-- PostgreSQL -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>

这样数据库组件倒是可以正常开发了,但是这样做的话,构建出的数据库组件,就同时包含了 mysql、oracle、postgresql 三种数据库的驱动包。应用服务引入了我们的数据库组件后,由于依赖传递,就也会同时引入了上面三种数据库驱动包,加大了应用服务打包后的体积,也浪费了资源,甚至可能出现依赖冲突、jar 包冲突的问题。

optional 就是为了解决上述问题。回到上面的场景,分析后发现,实际上的应用服务不太可能同时使用三种不同的数据库,它只需要自己用到的数据库的驱动包就可以了。比如一个使用了 mysql 的服务,是没有必要引入 oracle 的驱动包的。这时候 optional 就派上用处了。optional 表示该依赖是可选的,意味着构建后,标记为 optional 的依赖是不会被一起打入 jar 包的,也不会发生依赖传递。这样不仅我们自己的组件端体积变小了,应用服务也解决了引入过多无用依赖的问题。

<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<optional>true</optional>
</dependency>

<!-- oracle -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc14</artifactId>
<optional>true</optional>
</dependency>

<!-- PostgreSQL -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<optional>true</optional>
</dependency>

按照上面的方式将各个依赖设置为 optional 后,我们打包构建的 jar 将不再包含以上三种数据库的驱动,这样其他应用服务引入了我们的组件后,就不会默认带上上面的数据库驱动包。

可能有同学会疑虑,如果依赖没有带入,那么程序运行期间,会不会发生类找不到的问题?确实是这样的,实际上 optional 表示可选,它将选择权交给了上层应用服务。假如我的服务使用了 mysql,那么我就在服务自己的 pom.xml 中引入 mysql 的驱动包;如果用的是 oracle,同理也是自己引入 oracle 的驱动包,也就是按需使用。如果服务没有引入任何数据库驱动,那么最坏的情况应该是不会使用数据库相关的功能,而不是报错。

其实我理解这里 optional 更是一种规范,规范了以下行为:

  1. optional 通常用于组件端应用,即一个 jar 包,而不是一个应用服务;
  2. optional 的依赖不会一起被打包,不会经过依赖传递而引入上层应用服务中;
  3. optional 的依赖,表示可选,及时没有该依赖,也不影响程序运行,这里需要程序进行一些额外的判断逻辑;
  4. optional 的依赖,由上层应用服务来选择是否引入。

举个大白话的例子,去面馆吃面,那么面馆肯定不会把各种调料都放满,而是将选择权交给顾客,顾客按照自己的口味来添加调味料,加不加、加什么,都是顾客自己选。

provided

provided 其实与 optional 非常像,因为从结果来看,二者基本上没有什么区别:标记为 provided 的依赖,和 optional 其实效果是一样的,都不会被直接的打入包中,不会通过依赖传递而传递到上层应用服务的依赖中。那么为什么这里需要把 provided 与 optional 区分开呢?

这里其实,二者虽然在效果上没有直接的区别,但是二者的使用场景不一样。上面说了 optional 主要用于“可选依赖”的场景,依赖是否引入,都不会影响服务正常运行。而 provided 表示的不是依赖可选,它表示这个依赖是必须的,但是这个依赖通常是已经提供的,应用服务不需要额外引入,通常也不用关心。例如很多人使用 tomcat 作为服务运行容器,tomcat 自己会提供一些必备的依赖项,这些依赖项对于服务正常运行必须的,但是对于应用服务来说,这些依赖项是已经提供好的,不需要自己关心。

所以很多时候,会有人将 optional 与 provided 弄混,甚至一起使用,而且通常情况下是没有什么问题的。不过我们还是需要明白二者的区别。虽然二者的行为是一致的,但是对于二者规定的规范确实不同的:

  • optional 表示某个依赖可选,该依赖是否使用都不会影响服务运行。例子:吃面时候,酱油就是可选的,加不加都不会影响面的正常使用。
  • provided 表示某个依赖必须,不过该依赖通常是由系统或者容器提供,不需要自己关系。例子:吃面时候,筷子、碗这样的东西都是必须的,不过这些一般是店家给顾客备好,不需要顾客自带。