SPI加载机制
SPI(Service Provider Interface)是一种通过外界配置来加载具体代码内容的技术手段。SPI是JDK内置的一种服务提供发现机制,用于实现框架的扩展和组件替换。
在SPI中,框架提供一整套接口,使用者实现这些接口后,在classpath的目录META-INF/services/
下创建以该接口命名的文件,并在该接口中写下实现类的全包名,SPI的加载机制则会加载该文件中类名对应的类。
下面是一个简单样例
首先定义一个接口
package cn.bobasyu.spi;
public interface ISpiTest {
void test();
}
然后是这个接口的实现类
package cn.bobasyu.spi.impl;
import cn.bobasyu.spi.ISpiTest;
public class ASpiTestImpl implements ISpiTest {
@Override
public void test() {
System.out.println("Hello A!");
}
}
package cn.bobasyu.spi.impl;
import cn.bobasyu.spi.ISpiTest;
public class BSpiTestImpl implements ISpiTest {
@Override
public void test() {
System.out.println("Hello B!");
}
}
接着如前面所述的,在lasspath的目录META-INF/services/
下创建以该接口命名的文件,并在该文件中写入两个实现类的名称
文件中的内容:
cn.bobasyu.spi.impl.ASpiTestImpl
cn.bobasyu.spi.impl.BSpiTestImpl
在运行时,使用ADK自带的ServiceLoader进行加载,即可读取文件中的类名并加载好对应的对象
@Test
public void spiLoadTest() {
ServiceLoader<ISpiTest> serviceLoader = ServiceLoader.load(ISpiTest.class);
for (ISpiTest spiTest : serviceLoader) {
spiTest.test();
}
}
SPI在许多地方都有使用,比如在JDBC中,定义了java.sql.Driver
接口,接下来针对每个数据库的具体实现需要在META-INF/services/
中放入相应的文件,下面是mysql中的例子
文件中的内容:
使用SPI可以实现框架设计者和具体使用者间的解耦,在进行系统架构设计时,只关注抽象的部分,而框架的使用者则可以根据自己的需求进行自定义的扩展,其体现的是数据模式中的桥接模式,抽象部分与实现部分分离。
角色名 | 含义 |
---|---|
抽象化(Abstraction)角色 | 抽象化给出的定义,并保存一个对实现化对象的引用 |
修正抽象化(RefinedAbstraction)角色 | 扩展抽象化角色,改变和修正父类对抽象化的定义 |
实现化(Implementor)角色 | 这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作 |
具体实现化(ConcreteImplementor)角色 | 这个角色给出实现化角色接口的具体实现 |