1. 前言
想知道JDK中为第三方服务提供的接口
的实现类在双亲委托模式加载不了时,是如何进行类加载的吗?
想知道为什么现在几乎不用再在项目中配置数据库驱动类了吗?
那么请认真阅读
完该文章,你将得到你想要的答案!!!
2. Java SPI
什么是Java SPI?SPI是Service Provider Interface
的简称,也就是服务提供发现机制
;Java中的SPI就是一种将服务接口与服务实现分离,以达到松耦合、提高程序可扩展性为目的的编程思想;在JDK中最典型案例的莫过于JDBC模块;
Java SPI的约定:
- 定义一个接口;
- 在项目资源的META-INF目录下创建services目录,再在services目录下创建以接口全名为名字的文件,然后在该文件中写上接口实现类的全名,注意文件编码格式必须为UTF-8;
- JDK中提供了一个
ServiceLoader
类,可动态的加载通过SPI方式配置的接口的实现类;
3. 手写Java SPI实例
3.1 定义一个Animal
接口
package cn.jackiegu.jvm.study.spi;
/**
* 动物接口
*
* @author JackieGu
* @date 2021/4/23
*/
public interface Animal {
void move();
}
3.2 创建两个实现类Bird
和Cat
实现Animal
接口
package cn.jackiegu.jvm.study.spi;
/**
* 鸟
*
* @author JackieGu
* @date 2021/4/23
*/
public class Bird implements Animal {
@Override
public void move() {
System.out.println("小鸟:飞");
}
}
package cn.jackiegu.jvm.study.spi;
/**
* 猫咪
*
* @author JackieGu
* @date 2021/4/23
*/
public class Cat implements Animal {
@Override
public void move() {
System.out.println("小猫:跑");
}
}
3.3 创建Animal
接口的配置文件cn.jackiegu.jvm.study.spi.Animal
,并配置上接口的实现类全名
cn.jackiegu.jvm.study.spi.Bird
cn.jackiegu.jvm.study.spi.Cat
3.4 使用ServiceLoader
类进行测试
package cn.jackiegu.jvm.study.spi;
import java.util.ServiceLoader;
/**
* 测试类
*
* @author JackieGu
* @date 2021/4/23
*/
public class SPITest {
public static void main(String[] args) {
ServiceLoader<Animal> animals = ServiceLoader.load(Animal.class);
for (Animal animal : animals) {
animal.move();
}
}
}
// 运行结果
小鸟:飞
小猫:跑
4. Java SPI源码分析
备注:本文源码分析以ServiceLoader.load(Class<S> service)
方法作为入口;
4.1 ServiceLoader类介绍
/**
* ServiceLoader类实现了Iterable接口,表示该类实例可以通过迭代器遍历自身元素
* ServiceLoader类被final关键字修饰,表示该类不可被继承
*/
public final class ServiceLoader<S> implements Iterable<S> {
/**
* 提供程序类的配置文件路径前缀
*/
private static final String PREFIX = "META-INF/services/";
/**
* 正在加载的服务的类或接口
*/
private final Class<S> service;
/**
* 用于定位、加载和实例化提供程序类的类加载器
*/
private final ClassLoader loader;
/**
* 上下文权限控制器,一般用不到
*/
private final AccessControlContext acc;
/**
* 用于缓存提供者实例
*/
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
/**
* 惰性查找迭代器
*/
private LazyIterator lookupIterator;
// ......
/**
* 私有的构造方法
*/
private ServiceLoader(Class<S> svc, ClassLoader cl) {
// ......
}
// ......
/**
* 私有的内部迭代器类,实现完全惰性查找提供程序类
*/
private class LazyIterator implements Iterator<S> {
// ......
}
// ......
}
4.2 创建ServiceLoader实例
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取当前线程类加载器,该类加载器很重要,是定位提供程序类的配置文件和实例提供程序类的重要角色
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 调用重载方法
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader) {
// 调用私用构造方法创建实例
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
// 实例service属性,并判断其是否为null
service = Objects.requireNonNull(svc, "Service interface cannot be null");
// 实例loader属性,为null时使用ApplicationClassLoader
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
// 实例acc属性,默认情况SecurityManager是关闭的,所以acc一般为null
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
// 清空缓存
providers.clear();
// 实例惰性查找迭代器
lookupIterator = new LazyIterator(service, loader);
}
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
4.3 查找提供程序类,并实例化
// ServiceLoader实现的iterator方法
public Iterator<S> iterator() {
// 返回一个匿名的Iterator实例
return new Iterator<S>() {
// 已知的提供者迭代器,这个属性在ServiceLoader第二次及以上遍历时起作用
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
/**
* 是否还有下一个元素
*/
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
// 若已知的提供者没有,则从惰性查找迭代器去判断
return lookupIterator.hasNext();
}
/**
* 获取下一个元素
*/
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
// // 若已知的提供者没有,则从惰性查找迭代器去获取
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
private class LazyIterator implements Iterator<S> {
// 正在加载的服务的类或接口
Class<S> service;
// 用于定位、加载和实例化提供程序类的类加载器
ClassLoader loader;
// 提供程序类配置文件URL实例的枚举
Enumeration<URL> configs = null;
// 提供程序类迭代器
Iterator<String> pending = null;
// 下一个元素
String nextName = null;
// ......
private boolean hasNextService() {
if (nextName != null) {
return true;
}
// 首次调用时configs为null,则进行加载
if (configs == null) {
try {
// 提供程序类的配置文件完整路径
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 通过类加载器去获取配置文件URL实例枚举
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
// 首次调用时pending为null
while ((pending == null) || !pending.hasNext()) {
// 循环遍历提供程序类的配置文件URL实例枚举,将每一个配置文件URL实例遍历完成后才进行下一个,若没有配置文件URL实例了才返回false
if (!configs.hasMoreElements()) {
return false;
}
// 解析配置文件,并返回提供程序类迭代器
pending = parse(service, configs.nextElement());
}
// 获取下一个元素,并返回true
nextName = pending.next();
return true;
}
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
}
/**
* 判断是否还有下一个元素
*/
public boolean hasNext() {
if (acc == null) {
// 调用该方法处理
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
/**
* 获取下一个元素
*/
public S next() {
if (acc == null) {
// 调用该方法处理
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
5. Java SPI实战案例
5.1 创建一个自定义数据库驱动类实现JDBC的Driver接口
package cn.jackiegu.jvm.study.spi;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import java.util.logging.Logger;
/**
* 自定义数据库驱动类
*
* @author JackieGu
*/
public class Driver implements java.sql.Driver {
static {
try {
// 注意日志打印信息
System.out.println("The custom driver is loaded");
DriverManager.registerDriver(new Driver());
} catch (SQLException e) {
throw new RuntimeException("Can't register driver!");
}
}
@Override
public Connection connect(String url, Properties info) throws SQLException {
return null;
}
@Override
public boolean acceptsURL(String url) throws SQLException {
return false;
}
@Override
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
return new DriverPropertyInfo[0];
}
@Override
public int getMajorVersion() {
return 0;
}
@Override
public int getMinorVersion() {
return 0;
}
@Override
public boolean jdbcCompliant() {
return false;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
5.2 创建Driver接口的配置文件,并配置上刚刚创建的自定义数据库驱动类
cn.jackiegu.jvm.study.spi.Driver
5.3 在pom.xml中添加mysql依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
5.4 通过DriverManager获取数据库连接
public class SPITest {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8";
String username = "root";
String password = "123456";
Connection connection = DriverManager.getConnection(url, username, password);
System.out.println("DriverManager ClassLoader: " + DriverManager.class.getClassLoader());
System.out.println("Connection ClassLoader: " + Connection.class.getClassLoader());
System.out.println("Connection Instance ClassLoader: " + connection.getClass().getClassLoader());
System.out.println("Connection Instance: " + connection);
}
}
// 运行结果
The custom driver is loaded
DriverManager ClassLoader: null
Connection ClassLoader: null
Connection Instance ClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
Connection Instance: com.mysql.cj.jdbc.ConnectionImpl@38082d64
运行结果分析:
The custom driver is loaded
表示自定义数据库驱动类被加载;DriverManager
和Connection
是基础类,被Bootstrap ClassLoader加载是正常情况;Connection
的实例为com.mysql.cj.jdbc.ConnectionImpl
,但其加载器却为Application ClassLoader;
5.5 重点源码分析
/**
* DriverManager源码分析
*/
public class DriverManager {
// ......
static {
// 加载初始化驱动
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
// ......
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() {
// 加载Driver接口的实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
/*
* 获取Driver接口的实现类,这里会引起驱动实现类的静态块代码被执行,也就是运行结果分析第一点的原因
* ServiceLoader中加载实现类是通过当前线程的类加载器进行的,也就是运行结果分析第三点的原因
*/
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驱动jar包中同样存在一个Driver接口的配置文件
其内容如下:
com.mysql.cj.jdbc.Driver
spackage com.mysql.cj.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* mysql驱动类源码分析
*/
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!");
}
}
}
public class DriverManager {
/**
* 注册的JDBC驱动程序集合
*/
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
// ......
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 {
if(driver != null) {
// 如果该驱动程序未被添加过,则添加到注册驱动程序中
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
// ......
@CallerSensitive
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()));
}
// ......
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
// 获取调用类的类加载器,若调用类为null,则使用当前线程的类加载器
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
// 遍历注册的JDBC驱动程序集合
for(DriverInfo aDriver : registeredDrivers) {
// 判断JDBC驱动程序的类加载器与当前的是否一致,当一致时才进行数据库连接申请
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// 当数据库连接不为null时表示申请成功,进行返回
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
// 发生异常时记录异常
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// 若程序执行到这里,则已经表示申请数据库连接失败了
if (reason != null) {
println("getConnection failed: " + reason);
// 若记录的异常不为null,则抛出该异常
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
// 若记录的异常为null,则表示没有找到合适的数据库驱动类
throw new SQLException("No suitable driver found for "+ url, "08001");
}
}