SPI 原理
JDK版本为1.8
SPI
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。
系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦。
使用介绍
要使用Java SPI,需要遵循如下约定:
1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services
目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
2、接口实现类所在的jar包放在主程序的classpath中;
3、主程序通过java.util.ServiceLoder
动态装载实现模块,它通过扫描META-INF/services
目录下的配置文件找到实现类的全限定名,把类加载到JVM;
4、SPI的实现类必须携带一个不带参数的构造方法;
项目结构如下:
src/main
--------java
lzc.spidemo
api
Car.java
impl
BCCar.java
BMCar.java
Test.java
--------resource
META-INF.services
lzc.spidemo.api.Car
Car.java
public interface Car {
public String getCarName();
}
BCCar.java
public class BCCar implements Car {
@Override
public String getCarName() {
return "奔驰车";
}
}
BMCar.java
public class BMCar implements Car {
@Override
public String getCarName() {
return "宝马车";
}
}
META-INF/services/lzc.spidemo.api.Car
在META-INF/services/
下新建一个文件,名字为lzc.spidemo.api.Car
lzc.spidemo.impl.BCCar
lzc.spidemo.impl.BMCar
Test.java
public class Test {
public static void main(String[] args) {
// ServiceLoader.load(Car.class)
ServiceLoader<Car> carList = ServiceLoader.load(Car.class);
// ServiceLoader.iterator()
Iterator<Car> carIterator = carList.iterator();
while (carIterator.hasNext()) { // LazyIterator.hasNext()
Car car = carIterator.next(); // LazyIterator.next()
System.out.println(car.getCarName());
}
}
}
根据运行结果可以发现,ServiceLoader.load(Car.class)
可以帮我们找到Car
的实现类BCCar
和BMCar
,接下来通过原来来分析一下它是如何找到Car
的实现类的。
源码分析
Java的SPI机制实现跟ServiceLoader
这个类有关
public final class ServiceLoader<S> implements Iterable<S> {
// 读取配置文件的前缀路径 META-INF/services/
private static final String PREFIX = "META-INF/services/";
// 需要被加载的服务接口或者服务类
private final Class<S> service;
// 类加载器
private final ClassLoader loader;
// 创建ServiceLoader时采用的访问控制上下文,默认情况下为 null
private final AccessControlContext acc;
// 缓存SPI的实现,key是完整类名
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 当前的迭代器,默认初始化为: new LazyIterator(service, loader)
// 这里是懒加载的,只有使用的时候才去迭代,加载
// LazyIterator 是 ServiceLoader 的内部类
private LazyIterator lookupIterator;
}
可以看到,ServiceLoader
实现了Iterable
接口,覆写其iterator
方法能产生一个迭代器;同时ServiceLoader
有一个内部类LazyIterator
,而LazyIterator
又实现了Iterator
接口,说明LazyIterator
是一个迭代器。
ServiceLoader.load(Car.class)
从ServiceLoader.load(Car.class)
开始分析
// java.util.ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取当前线程上下文类加载器 AppClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 把刚才取出的线程上下文类加载器作为参数传入,用于后面去加载classpath中的外部厂商提供的驱动类
// 将service接口类和线程上下文类加载器作为参数传入,继续调用load方法
return ServiceLoader.load(service, cl);
}
查看ServiceLoader.load(service, cl)
// java.util.ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
// 将service接口类和线程上下文类加载器作为构造参数,创建一个ServiceLoader对象
return new ServiceLoader<>(service, loader);
}
查看new ServiceLoader<>(service, loader)
// java.util.ServiceLoader
private ServiceLoader(Class<S> 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()
方法
查看reload()
方法
// java.util.ServiceLoader
public void reload() {
// 清除缓存
providers.clear();
// 新建 LazyIterator
lookupIterator = new LazyIterator(service, loader);
}
在reload
方法中又新建了一个LazyIterator
对象,然后赋值给lookupIterator
。
// java.util.ServiceLoader
// LazyIterator 是 ServiceLoader的内部类
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
}
在构建LazyIterator
对象时,只是给其service
和loader
属性赋值,并没有去加载接口的实现类。
ServiceLoader.iterator()
查看ServiceLoader.iterator()
// java.util.ServiceLoader
public Iterator<S> iterator() {
// 这里返回的是一个匿名的迭代器对象
return new Iterator<S>() {
// 将 providers 缓存的数据转换成迭代器 Iterator 并赋值给 knownProviders
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
// 如果缓存中有数据就直接返回true
if (knownProviders.hasNext())
return true;
// 缓存中没有数据,调用 LazyIterator.hasNext()
return lookupIterator.hasNext();
}
public S next() {
// 如果缓存中有数据就直接从缓存中返回
if (knownProviders.hasNext())
return knownProviders.next().getValue();
// 缓存中没有数据,调用 LazyIterator.next()
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
LazyIterator.hasNext()
查看LazyIterator.hasNext()
// java.util.ServiceLoader.LazyIterator
// LazyIterator 是 ServiceLoader 的内部类
public boolean hasNext() {
// 默认情况下 acc = null
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
查看hasNextService()
方法
// java.util.ServiceLoader.LazyIterator
// LazyIterator 是 ServiceLoader 的内部类
// 这里会涉及到一个名词 "全限定名"
// 这里说的 "全限定名" = "包路径"."类名名称或者是接口名名称"
private boolean hasNextService() {
// 如果 nextName 不为空,则直接返回 true
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// PREFIX = "META-INF/services/"
// service.getName() 获取接口的全限定名
// 在构建LazyIterator对象时已经给其成员属性service赋值
String fullName = PREFIX + service.getName();
// 在构建LazyIterator对象时已经给其成员属性loader赋值
// 加载 "META-INF/services/接口的全限定名" 文件
if (loader == null)
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;
}
// 解析 "META-INF/services/接口的全限定名" 文件内容
// 返回 "META-INF/services/接口的全限定名" 文件内容中的服务提供者的全限定名并赋值给pending属性
pending = parse(service, configs.nextElement());
}
// 然后取出一个服务提供者全限定名赋值给LazyIterator的成员变量nextName
nextName = pending.next();
return true;
}
这里用到了懒加载的思想,当需要时才去加载配置文件。
该方法的主要功能是:去META-INF/services/
目录下加载接口文件的内容,解析文件内容赋值给LazyIterator
的成员变量pending
,pending
是一个Iterator
,然后取出第一个值赋值给LazyIterator
的成员变量nextName
。
比如META-INF/services/lzc.spidemo.api.Car
的文件内容为。
lzc.spidemo.impl.BCCar
lzc.spidemo.impl.BMCar
那么LazyIterator
的pending
属性就保存了lzc.spidemo.impl.BCCar
和lzc.spidemo.impl.BMCar
这两个值,然后取出lzc.spidemo.impl.BCCar
赋值给LazyIterator
的成员变量nextName
。
执行完LazyIterator
的hasNext
方法后,会继续执行LazyIterator
的next
方法
LazyIterator.next()
查看LazyIterator.next()
方法
// java.util.ServiceLoader.LazyIterator
// LazyIterator 是 ServiceLoader 的内部类
public S next() {
// 默认情况下 acc = null
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
查看LazyIterator.nextService()
方法
// java.util.ServiceLoader.LazyIterator
// LazyIterator 是 ServiceLoader 的内部类
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
// hasNextService()方法中为 nextNam e赋值过服务提供者实现类的全限定名
String cn = nextName;
// 将 nextName 设置为空
// 下次调用 hasNextService() 时会从 pending 获取值并赋值给 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集合进行缓存
providers.put(cn, p);
// 返回实例
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
LazyIterator.nextService()
利用Class.forName(String name, boolean initialize,ClassLoader loader)
加载服务提供者实现类,然后利用c.newInstance()
实例化,把实例化的类存储到providers
中缓存起来。
JDBC驱动加载
JDBC驱动加载是利用了Java的SPI机制。JDBC提供了一组接口规范,不同的数据库厂商只要编写符合这套JDBC接口规范的驱动代码,那么就可以用Java语言来连接数据库。
以MySQL为例分析JDBC驱动加载源码。
首先需要引入mysql-connector-java
依赖包,版本为8.0.20。mysql-connector-java.jar
包目录下的META-INF/services/
下有一个java.sql.Driver
文件,文件内容如下所示:
com.mysql.cj.jdbc.Driver
加载MySQL驱动类
public class DBUtils {
private static String dirverClassName = "com.mysql.cj.jdbc.Driver";
private static String url = "jdbc:mysql://127.0.0.1:3306/lzc?characterEncoding=utf8";
private static String user = "root";
private static String password = "root";
public static Connection getDBConnection() {
Connection conn = null;
// try {
// // 在JDBC 4.0 规范中可以省略这一步了
// // Class.forName 用来加载类信息并执行类的静态块
// Class.forName(dirverClassName);
// } catch (ClassNotFoundException e) {
// e.printStackTrace();
// }
try {
// 通过 DriverManager 获取 Connection
conn = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
}
从上面的代码可以发现,通过DriverManager.getConnection(String url,String user, String password)
可以获取Connection
连接信息,此时肯定会执行DriverManager
的静态块代码。
查看java.sql.DriverManager
静态代码:
// java.sql.DriverManager
public class DriverManager {
// 用来保存JDBC驱动列表
// 就是实现了 java.sql.Driver 接口的类在类加载时会主动注册到 registeredDrivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
}
查看loadInitialDrivers()
方法
// java.sql.DriverManager
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;
}
// 重点看这里
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 前面已经分析过这里的原理了
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// 调用 ServiceLoader 的 iterator 方法
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
// 在迭代的时候会去加载META-INF/services/java.sql.Driver文件,
// 文件内容为 com.mysql.cj.jdbc.Driver
// 然后实例化 com.mysql.cj.jdbc.Driver
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);
}
}
}
这里主要是利用了Java的SPI机制去实例化MySQL驱动类,下面查看MySQL驱动类是如何注册到DriverManager
类的registeredDrivers
集合中
驱动类注册到DriverManager
查看com.mysql.cj.jdbc.Driver
的静态块代码
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
// 这里会将 com.mysql.cj.jdbc.Driver 实例注册到
// DriverManager类的registeredDrivers集合中
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}
看到这里可以知道,com.mysql.cj.jdbc.Driver
在实例化的时候会执行它的静态块代码,此时会将自己注册到DriverManager
类的registeredDrivers
集合中。
MySQL驱动连接数据库
com.mysql.cj.jdbc.Driver
已经注册到DriverManager
类的registeredDrivers
集合中,下面查看它是何时被调用的。
通过前面的例子可以知道,通过DriverManager.getConnection(String url,String user, String password)
可以获取Connection
连接信息,查看这个方法的代码:
// java.sql.DriverManager
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()));
}
继续往下看
// java.sql.DriverManager
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
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;
// 主要查看这里
// 遍历 registeredDrivers 集合
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);
// 只要连接上就直接放回 Connection,如果有多个驱动类,其余的就会被忽略
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");
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: