从maven依赖定义顺序到Java spi机制,这些你忽略了的细节

2023-10-22 15:13:32 浏览数 (1)

从maven依赖定义顺序到Java spi机制,这些你忽略了的细节

一、起因

故事是这样的,新建一个SpringBoot项目的时候,把依赖都加进去之后,run起来,报错了!!

代码语言:javascript复制
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'defaultValidator' 
defined in class path resource [org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.class]: 
Invocation of init method failed; nested exception is java.lang.AbstractMethodError: 
org.apache.bval.jsr303.ConfigurationImpl.getDefaultParameterNameProvider()Ljavax/validation/ParameterNameProvider;

震惊!同样的依赖和配置竟然跑出不同的结果!

查原因:我加入的hibernate-validator竟然无效。

经过一系列试探,得出结论:两个依赖的顺序写反了,导致hibernate-validator并没有起作用。那么,到底是为什么呢? 这就牵扯到了maven依赖定义顺序和Java spi机制,请耐心观看下面的讲解。

如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以加入我们的java学习圈,点击即可加入,共同学习,节约学习时间,减少很多在学习中遇到的难题。

二、maven相同jar包的依赖顺序

我们在工作中的项目都是分模块的,而且模块之间又互相依赖,这个时候我们可能会引入相同的依赖 ,这时maven取那个依赖呢?这就是maven依赖的原则:

  1. 路径不同间接依赖中maven采用的是路径最短者优先

顾名思义,就是谁短谁先,一个项目test依赖了a和b两个jar包。其中a-b-c1.0, d-e-f-c1.1 。由于c1.0路径最短,所以项目test最后使用的是c1.0。

  1. 路径相同间接依赖中maven采用的是依赖定义顺序从上到下

如果 a-b-c1.0 , d-e-c1.1 这样路径都一样怎么办?其实maven的作者也没那么傻,会以在pom文件中申明的顺序那选,如果pom文件中先申明了d再申明了a,test项目最后依赖的会是c1.

所以maven依赖原则总结起来就两条:路径最短,申明顺序其次。

可以理解为,按顺序解析依赖,并记录下路径长度,然后更短的去覆盖。

然而,这次的错误跟这个并没有关系。

三、maven打包顺序

上面已经提到,路径相同,间接依赖中maven采用的是依赖定义顺序从上到下,那不同jar包,顺序是怎样的呢?

很明显,从上到下不是更容易控制么?和上面的理解一样,按顺序解析依赖,并记录下路径长度,然后更短的去覆盖。

日常中多模块打包时候,我们需要打包完依赖子模块,才能继续打包后面的模块。

所以,这里我们发现,hibernate-validator是在org.apache.bval.jsr303.ApacheValidationProvider之后的,这样,hibernate-validator的文件就不会去覆盖META-INF/services下的javax.validation.spi.ValidationProvider文件。

如图,

这个文件是干什么用的呢,这就牵扯到了Java spi机制。

四、Java spi机制

SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制,常用于创建可扩展、可替换组件的应用程序,是java中模块化插件化的关键。这些SPI的接口是由Java核心库来提供,而SPI的实现则是作为Java应用所依赖的jar包被包含进类路径(CLASSPATH)中。例如:JDBC的实现mysql就是通过maven被依赖进来。

SPI 框架包含3个基本组件:

  • 服务接口 Service Interface
  • 服务接口的实现类,Service Provider
  • 服务加载器 Service Loader

一张图,教你怎么使用Java spi:

使用方法:

  1. 代码编写: 既然是spi,那么就必须先定义好接口。其次,就是定义好接口的实现类。
  2. 创建一个文件夹: 在项目的srcmainresources下创建META-INFservices目录,类似上面hibernate-validator的文件夹。hibernate-validator的文件的META-INF/services:

3.文件夹下增加配置文件: 在上面META-INFservices的目录下再增加一个配置文件,这个文件名必须以接口的全限定类名保持一致,例如:javax.validation.spi.ValidationProvider,文件中写明实现类

4.使用JDK来载入 使用JDK提供的ServiceLoader.load()来加载配置文件中的描述信息,完成类加载操作。

代码语言:javascript复制
ServiceLoader<HelloService> loaders = ServiceLoader.load(HelloService.class);
for (HelloService helloService : loaders) {
    helloService.hello();
}

四、结论

JAR包依赖顺序影响相同实现的spi的顺序,调整下就行了。

0 人点赞