概述
概述
介绍SPI之前,我们先了解一下为什么要用SPI
JDBC相信已经不陌生了,JDBC 是一个标准。不同的数据库厂商(如,mysql、oracle等)会根据这个标准,有它们自己的实现。 既然,JDBC 是一个标准,那么 JDBC 的接口,应该就已经存在于JDK 中了,以前我们在使用JDBC的时候,都是需要加载Driver驱动的,如:
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql:///test";
Connection connection = = DriverManager.getConnection(url,"root","123456");
- 但是我们如果没有写的这行代码,也是可以让com.mysql.jdbc.Driver正确加载的,即:
String url = "jdbc:///test";
Connection connection = = DriverManager.getConnection(url,"root","123456");
- 那么这是为什么呢?要知道DriverManager类是由启动类加载器加载,而且根据全盘负责委托机制,每个类都有自己的类加载器,那么负责加载当前类的类加载器也会去加载当前类中引用的其他类,前提是引用的类没有被加载过。例如ClassA中有个变量 ClassB,那么加载ClassA的类加载器会去加载ClassB,如果找不到ClassB,则异常。
- 根据以上特性,那么JDK中的DriverManager启动类加载器会尝试去加载MySql的jar包,但明显是找不到的,因为它根本不在JDK中,那我们不妨看一下DriverManager的源码:
- 继续查看一下其中的 loadInitialDrivers() 方法:
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// 1
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) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
// 2
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
// 3
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
分析其中两个地方:
- 1、这里使用了ServiceLoader机制来加载驱动,它是
Java
提供的一套 SPI(Service Provider Interface) 框架,用于实现服务提供方与服务使用方解耦 - 2、使用 jdbc.drivers 定义的驱动名加载驱动
- 3、ClassLoader.getSystemClassLoader() 就是应用程序类加载器
规则
SPI机制是JDK提供接口,第三方Jar包实现,接口由启动类加载器加载,实现类不在JDK中,需要反向委派,由线程上下文加载器加载。它约定:在 jar 包的 META-INF/services 包下,以接口全限定名为文件名,文件内容是实现类名称
- 这样便可以使用刚才loadInitialDrivers这个方法
ServiceLoader<接口类型> allImpls = ServiceLoader.load(接口类型.class);
Iterator<接口类型> iter = allImpls.iterator();
while(iter.hasNext()) {
iter.next();
}
- 来得到具体的Driver实现类,那我们再追一下ServiceLoader是如何通过Driver.class接口来加载它具体的实现类的,现在进入 load() 方法:
public static <S> ServiceLoader<S> load(Class<S> service) {
//获取到了线程上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
- 线程上下文类加载器是当前线程使用的类加载器,默认就是应用程序类加载器,那么这个方法中的load方法就会使用刚才拿到的线程上下文类加载器去加载目标实现类,不过这个方法比较深,真正加载的具体代码在 ServiceLoader 的内部类 LazyIterator 的nextService方法中:
自定义实现
注解
package com.phz.prpc.extension;
import java.lang.annotation.*;
/**
* <p>
* {@code SPI}注解,可运行其他第三方实现的抽象接口需使用此注解
* </p>
* </br>
* <p>
* {@code JDK}的{@code SPI}机制是{@code JDK}提供接口,第三方{@code jar}包实现,接口由启动类加载器加载,实现类不在{@code JDK}中,需要反向委派,由线程上下文加载器加载。
* </p>
* </br>
* <p>
* 它约定:在 {@code jar} 包的 {@code META-INF/services} 包下,以接口全限定名为文件名,文件内容是实现类名称
* </p>
* </br>
* <p>
* 那么我们完全可以参照它的思想取仿写一个
* </p>
*
* @author PengHuanZhi
* @date 2022年01月16日 17:50
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Spi {
}
基于SPI的伪类加载器
package com.phz.prpc.extension;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* <p>
* 自己实现一个扩展类加载器辅助类
* ,区别于{@code JDK}的{@code SPI}机制,我们预定好在 {@code jar} 包的 {@code META-INF/extensions} 目录下方存放扩展类文件,文件内容就为第三方实现的全路径
* </p>
*
* @author PengHuanZhi
* @date 2022年01月16日 17:56
*/
@Slf4j
@Data
public final class ExtensionLoader<T> {
/**
* 约定第三方实现配置文件目录
**/
private static final String SERVICE_DIRECTORY = "META-INF/extensions/";
/**
* 接口的类型,用于获取此接口下的第三方实现
**/
private final Class<?> type;
/**
* 通过接口的{@link Class}对象获取其第三方实现类的加载器
*
* @param type 接口的类型
* @return ExtensionLoader<T> 返回一个指定接口类型的类加载器辅助类
**/
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Spi需要知道你想要找到哪个功能的第三方实现!");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("只支持寻找接口类型的第三方实现!");
}
if (type.getAnnotation(Spi.class) == null) {
throw new IllegalArgumentException("目标接口必须被@Spi注解标注!");
}
return new ExtensionLoader<>(type);
}
/**
* 获取这个接口指定名称的第三方实现对象
*
* @return T 返回目标实现
**/
public T getExtension() {
// 加载到一个第三方实现
Class<T> clazz = loadExtensionFile();
if (clazz == null) {
return null;
}
try {
return clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("实例化失败 : " + clazz);
}
}
/**
* 加载约定好的目录下方的名称为接口全路径的扩展文件
*
* @return Class<T> 返回目标第三方实现的{@link Class}对象
**/
private Class<T> loadExtensionFile() {
//想要获取谁的实现类
String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();
try {
Enumeration<URL> urls;
ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
urls = classLoader.getResources(fileName);
if (urls != null) {
URL resourceUrl = urls.nextElement();
return loadResource(classLoader, resourceUrl);
}
return null;
} catch (IOException e) {
log.error(e.getMessage());
return null;
}
}
/**
* 读取扩展文件的内容,找到第三方实现的全路径,并获得其{@link Class}对象
*
* @param classLoader 扩展类加载器辅助类的类加载器
* @param resourceUrl 文件在资源{@code URL}
* @return Class<T> 返回目标{@link Class}对象
**/
@SuppressWarnings("unchecked")
private Class<T> loadResource(ClassLoader classLoader, URL resourceUrl) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
// 可能是注释
final int ci = line.indexOf('#');
//如果是第一个位置,则这一行都可以不用解析了
if (ci == 0) {
continue;
} else if (ci > 0) {
//如果非第一个位置,需要将注释前面的内容取出来,也就是将注释后面的内容截取
line = line.substring(0, ci);
}
return (Class<T>) classLoader.loadClass(line.trim());
}
} catch (IOException | ClassNotFoundException e) {
log.error(e.getMessage());
return null;
}
return null;
}
}
测试
参考如下方式:
代码中体现(因为自定义的SPI机制用于笔者自己的项目下方,所以读者可以仅关注代码中的11行即可):
/**
* 使用负载均衡算法从服务集合中选取一个服务
*
* @param serviceInstances 服务集合
* @return InetSocketAddress 选取的服务
**/
public InetSocketAddress doChoice(List<InetSocketAddress> serviceInstances) {
String loadBalanceAlgorithm = prpcProperties.getLoadBalanceAlgorithm();
LoadBalance loadBalance;
try {
loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension();
if (loadBalance == null) {
loadBalance = LoadBalanceAlgorithm.valueOf(loadBalanceAlgorithm);
}
} catch (IllegalArgumentException e) {
log.error("未知的负载均衡算法:{},异常信息为:{}", loadBalanceAlgorithm, e.getMessage());
throw new PrpcException(ErrorMsg.UNKNOWN_LOAD_BALANCE_ALGORITHM);
}
return loadBalance.doChoice(serviceInstances);
}
最后
以上就是热心芹菜为你收集整理的JDK SPI机制以及自定义SPI类加载概述规则自定义实现的全部内容,希望文章能够帮你解决JDK SPI机制以及自定义SPI类加载概述规则自定义实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复