我們知道雙親委派模型很好的解決了各個類加載器基礎類型的一致性問題,而基礎類型往往是被用戶代碼繼承、調用的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);
}
}
總結:
可以通過設置線程上下文類加載器的方式,實現父類加載器請求子類加載器完成類加載,破壞雙親委派原則。