Java中的SPI是怎么一回事
SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
我先举例如何使用java的spi。
1. 首先定义一个服务接口,比如LogService.java
2. 再定义三个LogService接口的实现类
注意:我把三个实现类(StdOutLogServiceImpl.java,FileLogServiceImpl,MysqlLogServiceImpl)一个代码框里了
3. 在项目src
目录下新建一个META-INF/services
文件夹,然后再新建一个以LogService接口的全限定名命名的文件code.classloader.LogService
,其文件内容为:
4. 最后我们再新建一个测试类LogClientTest
:
运行测试类,结果如下图所示:
5. Java的SPI机制的源码分析
从测试类LogClientTest我们看到Java的SPI机制实现跟ServiceLoader
这个类有关,那么我们先来看下ServiceLoader
的类结构代码:
这下知道为啥要把案例中约束目录名META-INF/services/固定死了吧。
可以看到,ServiceLoader
实现了Iterable
接口,覆写其iterator
方法能产生一个迭代器;同时ServiceLoader
有一个内部类LazyIterator
,而LazyIterator
又实现了Iterator
接口,说明LazyIterator
是一个迭代器。
5.1 ServiceLoader.load方法,为加载服务提供者实现类做前期准备
我们开始探究Java的SPI机制的源码, 先来看LogClientTest
的第一句代码
ServiceLoader.load(LogService.class)
的源码如下:
我们继续往下看ServiceLoader.load(service, cl)
方法:
继续接着看new ServiceLoader<>(service, loader)
是如何构建的?
可以看到在构建ServiceLoader
对象时除了给其成员属性赋值外,还调用了reload
方法:
可以看到在reload
方法中又新建了一个LazyIterator
对象,然后赋值给lookupIterator
。
可以看到在构建LazyIterator
对象时,也只是给其成员变量service
和loader
属性赋值。
5.2 ServiceLoader.iterator方法,实现服务提供者实现类的懒加载
我们现在再来看LogClientTest
的第二句代码
,执行这句代码后最终会调用serviceLoader
的iterator
方法:
可以看到调用serviceLoader
的iterator
方法会返回一个匿名的迭代器对象,而这个匿名迭代器对象其实相当于一个门面类,其覆写的hasNext
和next
方法又分别委托LazyIterator
的hasNext
和next
方法来实现了。
我们继续追踪代码,发现接下来会进入LazyIterator
的hasNext
方法:
然后继续跟进hasNextService
方法:
可以看到在执行LazyIterator
的hasNextService
方法时最终将去META-INF/services/
目录下加载接口文件的内容即加载服务提供者实现类的全限定名,然后取出一个服务提供者实现类的全限定名赋值给LazyIterator
的成员变量nextName
。到了这里,我们就明白了LazyIterator
的作用真的是懒加载,在用到的时候才会真正去加载服务提供者实现类。
同样,执行完LazyIterator
的hasNext
方法后,会继续执行LazyIterator
的next
方法:
我们继续跟进nextService
方法:
可以看到LazyIterator
的nextService
方法最终将实例化之前加载的服务提供者实现类,并放进providers
集合中,随后再调用服务提供者实现类的方法。注意,这里是加载一个服务提供者实现类后,若main
函数中有调用该服务提供者实现类的方法的话,紧接着会调用其方法;然后继续实例化下一个服务提供者类。
因此,我们看到了ServiceLoader.iterator
方法真正承担了加载并实例化META-INF/services/
目录下的接口文件里定义的服务提供者实现类。
想了解SpringBoot的SPI机制的样板,META-INF/spring.factories,算是一种约定,也可以参考下
SpringBoot扩展点之EnvironmentPostProcessor https://blog.csdn.net/dong19891210/article/details/106436364
总结: 如果你看懂了java的spi,那么spring boot、dubbo的spi也能搞懂了,变体(当看到一样事物的内在逻辑,就要学会润色、加工、处理、改造、完善,灵活变通、举一反三)!!!
附代码目录结构:
参考:
0. java.util Class ServiceLoader https://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html
-
Java是如何实现自己的SPI机制的? JDK源码(一) https://mp.weixin.qq.com/s/6BhHBtoBlSqHlXduhzg7Pw
-
Spring-SpringFactoriesLoader详解 https://msd.misuland.com/pd/2884250137616453978
-
探讨注解驱动Spring应用的机制,详解ServiceLoader、SpringFactoriesLoader的使用(以JDBC、spring.factories为例介绍SPI) https://cloud.tencent.com/developer/article/1497777
-
Dubbo源码解析之SPI(一):扩展类的加载过程 https://blog.51cto.com/14159827/2475733?source=drh
-
Java Code Examples for org.springframework.core.io.support.SpringFactoriesLoader https://www.programcreek.com/java-api-examples/index.php?api=org.springframework.core.io.support.SpringFactoriesLoader
-
Java Service Loader vs Spring Factories Loader https://blog.frankel.ch/java-service-loader-vs-spring-factories/
-
JDK的SPI原理及源码分析 https://mp.weixin.qq.com/s?__biz=MzI1MjQ2NjEyNA==&mid=2247483671&idx=1&sn=6d6ea78a1d7fd7ef0fb2a3f948bdca99