这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战
前言
之前看了spring带的SpringFactoriesLoader,他是spring框架下的一个工具类,是用来发现META-INF目录下的spring.factories文件的。 JDK自带了一个和SpringFactoriesLoader功能类似的一个工具类,叫ServiceLoader,不过这个类是发现项目中META-INF/services/目录下的文件的。 JDK自带的这种机制叫做SPI(Service Provider Interface),是Java提供给外部扩展或三方实现的一个机制。
正文
JDK SPI使用
使用方式也比较简单,ServiceLoader的注释上也都有说明:
- 服务方定义好一个接口或抽象类
- 三方或扩展方进行实现
- 三方或扩展方在jar的META-INF/services/目录下,创建一个 ‘接口类的全限定名’ 的文件
- 文件中的类容就是接口的实现类的全限定名,一行只能有一个,多个就多行。
下面进行自定义的步骤,这里为了方便,接口和实现就全在一个包里面的:
- 定义接口,所在包a.b.c.d 代码也比较简单,就是一个接口,其中定义一个方法。
public interface TestService {
void test();
}
- 实现,所在包:a.b.c.d.impl,实现接口即可,自定义内容
public class TestServiceImpl implements TestService {
public void test() {
System.out.println("TestServiceImpl service run");
}
}
-
在META-INF/services/目录下创建
a.b.c.d.TestService文件,然后内容就是a.b.c.d.impl.TestServiceImpl -
调用
public class ServiceLoaderTest {
public static void main(String[] args) {
ServiceLoader<TestService> load = ServiceLoader.load(TestService.class);
Iterator<TestService> iterator = load.iterator();
while (iterator.hasNext()) {
TestService next = iterator.next();
next.test();
}
}
}
一次性加载了定义的文件中的所有的实现类,需要用迭代器进行迭代。
这样一个使用JDK的SPI机制的demo就完成了。
JDK SPI案例
第一个案例就是JDBC的DRIVER了,打开Driver,这个类是定义在JDK的java.sql下的。 然后引入MySQL的包:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
会在该包下的META-INF/services/目录下发现一个java.sql.Driver 文件,内容是com.mysql.cj.jdbc.Driver。 然后打开这个类:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
}
}
然后打开DriverManager类,发现有个静态代码块,
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
.... 省略 ...
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
}
return null;
}
});
... 省略...
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
上面的代码主要就两个步骤,1. 使用ServiceLoader加载,然后调用 Class.forName()。
最后
看了JDK的SPI机制,收获还是挺大的。总结一下:
- ServiceLoader是根据接口加载文本中所有的实现类,若只需要其中一个实现类,则需要匹配需要的那个实现类
- 定义了一个标准,由扩展方或第三方去进行实现,服务方则不用去关注具体怎么实现的,实现解耦。
- ServiceLoader和SpringFactoriesLoader功能是类似的,只是一个是属于JDK的,一个是属于spring框架的