我们知道双亲委派模型很好的解决了各个类加载器基础类型的一致性问题,而基础类型往往是被用户代码继承、调用的API存在,但这总设计有时候并不是完美的,比如基础类型又要调用用户的代码,那该怎么办?
典型的例子就是SPI机制的实现,以JDBC的实现为例。
JDK提供了统一的JDBC驱动接口Driver,各种数据库厂商(MySQL、Oracle等)会根据SPI规范,以jar包形式提供自己的实现。对于实现类的查找与加载本就属于JDK的职责范畴,但是这些实现类都存在于classpath路径下,顶层的双亲类加载器是无法找到的,需要借助子加载器AppClassLoader才能加载,但这又违背双亲委派原则。
为了解决这个问题,Java设计师引入了一种线程上下文类加载器(Thread Context ClassLoader),可以通过Thread类的setContextClassLoader()方法进行设置,如果没有设置,默认就是AppClassLoader。这样便可以可以利用线程上下文加载器去加载SPI的实现类,实现一种父类加载器请求子类加载器完成类加载的行为。虽然这样违背了双亲委派原则,但也解决了一些特殊需求。
通过查看源码验证一下这个过程:
首先通过JDK提供的驱动管理类DriverManager获取一个数据库连接。
public static void main(String[] args) throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test");
}
主要看一下DriverManager的静态代码块中初始化JDBC驱动的逻辑,这里包含了通过ServiceLoader.load加载SPI实现类的过程。
public class DriverManager {
...
static {
// 加载初始JDBC驱动程序
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() {
// 通过ServiceLoader.load加载SPI实现类
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);
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);
}
}
}
}
ServiceLoader是Java SPI实现的加载器,默认从META-INF/services/路径下路径SPI配置文件,获取具体实现类。
load方法通过Thread.currentThread().getContextClassLoader()获取线程上下文类加载器,默认情况下是AppClassLoader,这样便可以加载classpath下的实现类了。
public final class ServiceLoader<S> implements Iterable<S> {
private static final String PREFIX = "META-INF/services/";
...
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
// 如果未指定具体的线程上下文类加载器,则使用系统当前的类加载器AppClassLoader
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
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);
}
}
总结:
可以通过设置线程上下文类加载器的方式,实现父类加载器请求子类加载器完成类加载,破坏双亲委派原则。