服务提供者接口的意思
简单理解:SPI机制为一种服务扩展机制,首先在配置文件中定义好接口的实现类,然后根据这个接口从配置文件中加载该接口的所有实现类,以供使用。
开发常见示例:
JDBC驱动加载:根据不同的数据库厂商加载不同的JDBC驱动包
SpringBoot的SPI机制:在META-INF下的spring.factories中加上自定义的自动配置,事件监听器或初始化器等
Dubbo的SPI机制:如路由扩展,负载均衡扩展,集群扩展等
自定义一个接口
public interface DriverInterface {
public void hello();
}
编写两个测试的接口实现类
public class MyDriver implements DriverInterface {
@Override
public void hello() {
System.out.println("My driver 1");
}
}
public class MyDriver2 implements DriverInterface {
@Override
public void hello() {
System.out.println("My driver 2");
}
}
在resource下创建META-INF/services文件夹
并创建以接口的全限定名命名的文件
//内容包含该接口具体实现类的全限定名,以下为文件内容
com.coolcoding.boot.spi.MyDriver
com.coolcoding.boot.spi.MyDriver2
测试SPI
public class SpiClient {
public static void main(String[] args) {
// 使用java.util.ServiceLoader 根据接口.class来加载
ServiceLoader load = ServiceLoader.load(DriverInterface.class);
//输出测试
load.forEach(DriverInterface::hello);
}
}
测试结果如下:顺利打印两个实现类具体方法的处理
My driver 1
My driver 2
从上一步的SpiClient中ServiceLoader.load(DriverInterface.class);从这一步进入分析是如何加载的
public static ServiceLoader load(Class service) {
//获取当前线程的context类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
new 一个serviceLoader
public static ServiceLoader load(Class service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
构造器中初始化一些变量,然后执行reload()
private ServiceLoader(Class svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
reload():创建一个LazyIterator
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
//并初始化lazyIterator的变量属性
private LazyIterator(Class service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
如何去加载之前创建的META-INF/services文件夹中的实现类?
LazyIterator:懒加载迭代器,可以从名字中注意到在迭代器迭代的时候去加载指定接口的实现类,实现懒加载!
查看LazyIterator中的迭代方法
public boolean hasNext() {
if (acc == null) {
//进入hasNextService()方法
return hasNextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
hasNextService()
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//PREFIX : private static final String PREFIX = "META-INF/services/";
//service.getName() 即接口的全限定名
String fullName = PREFIX + service.getName();
if (loader == null)
//加载META-INF/services下的接口文件中的服务实现类
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
结论:
至此调LazyIterator的hasNextService方法时最终将去META-INF/services/目录下加载接口文件的内容即加载服务提供者实现类的全限定名,然后取出一个服务提供者实现类的全限定名赋值给LazyIterator的成员变量nextName。
思考:为何在此处使用懒加载?
查看类似迭代器在while()中的处理,执行完hasNextService之后会执行nextService()方法
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class> c = null;
try {
//使用反射加载
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
可以看到了ServiceLoader.iterator方法真正承担了加载,使用反射方式实例化META-INF/services/目录下的接口文件里定义的服务提供者实现类
典型示例:JDBC驱动加载
查看java的核心库(rt.jar)有java.sql.Driver接口和java.sql.DriverManager类
Driver:各个数据库厂商的驱动类要实现的接口
DriverManager:用来管理数据库的驱动类的,值得注意的是DriverManager这个类有一个registeredDrivers集合属性,用来存储Mysql的驱动类
如引入MySQL,则mysql的驱动包有com.mysql.jdbc.Driver类
public class Driver extends com.mysql.cj.jdbc.Driver {
public Driver() throws SQLException {
}
static {
System.err.println("Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.");
}
}
查看com.mysql.cj.jdbc.Driver源码
实现了java的核心库(rt.jar)有java.sql.Driver接口,该步骤则契合了最开始的示例中实现类需要实现指定的接口
//实现了implements java.sql.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
在JDBC编程中我们需要通过DriverManager获取一个连接,而DriverManager有静态代码块,则会先执行DriverManager中的静态代码块
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "123456");
DriverManager的静态代码块
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
loadInitialDrivers():加载初始化驱动器
其中同样使用了ServiceLoader
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
//该处熟悉的地方:采用ServiceLoader加载Driver接口的具体实现类,即数据库驱动实现类
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
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);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
结论
Mysql的驱动类加载主要也是利用JAVA的SPI机制,即ServiceLoader来实现加载并实例化Mysql的驱动类
在什么时候Mysql的驱动类被注册到DriverManager的registeredDriver集合中?
com.mysql.jdbc.Driver也有个静态代码块
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
//注册驱动类
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
DriverManager.registerDriver():
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
结论:
在Driver中的静态代码块中会往DriverManager中注册驱动类。
驱动类在哪里被使用到?
从DriverManager获取连接中可以分析获取连接需要加载驱动类
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "123456");
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
getConnection()
private static Connection getConnection(
String url, java.util.Properties info, Class> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
//************此处for循环从registeredDrivers list中获取到之前加载的驱动类
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
结论:
至此分析了驱动类如何去加载以及在哪一步中使用到了驱动类。
在Dubbo中也有采用SPI机制,如其中的源码分析中Protocol下有DubboProtocol和RegistryProtocol,这两个类在Dubbo中的服务暴露和服务引用中起着很关键的作用,详细可以查看Dubbo官网源码分析