SPI(service provider Interfaces),对于Java中的实现SPI类ServiceLoader
来讲,就是Java类库里的代码能够调用外部实现类的代码。听起来是不是很抽象。那从原理上来讲如何实现呢?内部调用外部实现类又有什么用呢?在这里我先回答第二个问题:
Q:内部调用外部实现类又有什么用?
A: 我认为有下面两点原因
- 试想JDK提供一个接口
interface Driver{}
,但是它不想自己实现,因为没有一个固定的实现方式,比如mysql
中的数据库驱动加载。于是这个接口留给实现者实现。然后JDK在内部为执行一系列的接口方法(注意这个接口已经被外部类实现,至于如何加载外部类,这就是JDK里的ServiceLoader
做的事情了),就能提供灵活的实现方式。 - 运用上面的思想,我们可以实现A模块引用B模块,A可以调用B中的任何函数,且B模块也能实现调用A模块的函数。实现方式是B(可以类比上述JDK)提供一个接口
Interface IB
,然后让A模块去实现这个接口。最后通过SPI技术,B能够加载A中的实现类,从而第调用了A中实现类的函数
Q:SPI技术原理是什么呢?
A:运用类加载技术,根据上面想法,我们可知关键点在于B如何加载A中的实现类的。从系统思维角度来讲A引用了B,B就肯定会获得A的某些信息。废话不说,直接上如何实现这样场景的代码:
先看项目路径
image.png
child1的实现:
定义接口
package spi;
public interface DemoService {
public String sayHi(String msg);
}
在META-INF.services/spi.demoService
写如下代码,表示实现类的路径,实现类必须要按照该字段定义的路径来
spi.ChineseDemoServiceImpl
spi.EnglishDemoServiceImpl
spiMain.java:该类是实现child1调用child2的重要类
package spi;
import java.util.Iterator;
import java.util.ServiceLoader;
public class SpiMain {
public static void getConnection(){
ServiceLoader<DemoService> serviceLoader = ServiceLoader.load(DemoService.class);
Iterator<DemoService> services = serviceLoader.iterator();
while (services.hasNext()) {
DemoService service = services.next();
System.out.println(service.sayHi("world"));
}
}
}
我们需要讲child1模块打包成.jar格式放到child2模块中。
child2:
下面是两个接口实现类,当然我也可以在这个类里面做其他事情,以便child1模块可以更多的调用
ChineseDemoServiceImpl.java
package spi;
public class ChineseDemoServiceImpl implements DemoService {
@Override
public String sayHi(String msg) {
return "你好" + msg;
}
}
EnglishDemoServiceImpl.java
package spi;
public class EnglishDemoServiceImpl implements DemoService {
@Override
public String sayHi(String msg) {
return "hello" + msg;
}
}
child2Main.java
package spi;
public class Child2Main {
public static void main(String[] args) {
SpiMain.getConnection();//调用child1的函数,初始化自己提供的服务实现类
}
}
从以上代码,我们就能比较具体的看到SPI的作用了。也能回答之前提出的问题了,是通过在文件里指定路径名的方式,去搜索的实现类。但是问题在于ServiceLoader
是JDK里面的呀,是通过Extension ClassLoader
加载的呀,这个加载器如何去加载应该有App ClassLoader
加载的.class文件呢?原来其引入了一个线程上下文类加载器(该加载器是App ClassLoader),强制破坏了双亲委派模型层层加载的原则,在ServiceLoader里直接请求上下文加载器去加载指定路径的.class
文件。
好了,这一节先说到这里,我会在原理解析二中,详细展开ServiceLoader
的源代码。
网友评论