vlambda博客
学习文章列表

2021007期:双亲委派模型

保护私人版权,尊重他人版权。转载请注明出处并附带页面链接

概念

双亲委派模型是指当类加载器收到类加载任务时,会先交由自己的父类加载器去尝试加载,最终会传递到BootstrapClassLoader,只有当父类加载器无法加载任务时才会尝试自己加载。

常见类加载器

BootstrapClassLoader(启动类加载器):负责核心类库的加载,rt.jar,java.lang.*,JVM_HOME/lib目录下的,构造ExtClassLoader和AppClassLoader。

ExtClassLoader(拓展类加载器):负责加载jre/lib/ext的jar。

AppClassLoader(系统类加载器):加载应用程序的主函数类。

双亲委派模型的打破

Java SPI机制:SPI全称Service Provider Interface,用于接入产商自定义组件,例如JDBC。SPI的思想是系统抽象的各个模块有多种实现方案,例如JDBC,日志模块等。面向对象中,我们倾向于基于接口编程,模块直接不对实现类进行硬编码,否则会破坏开闭原则。为了能在模块装配的时候不需要在程序中显示声明,需要服务发现机制,SPI就是提供这样一个服务。

SPI的约定:当服务提供者提供服务接口后,在jar包下的META-INF/services/目录同时创建一个以服务接口命名的具体实现类文件。当外部程序装配模块的时候,能够以META-INF/services/里的配置文件找到具体的实现类并实例化。jdk中提供了java.util.ServiceLoader进行实现。

以JDBC为例,我们通常用以下方式获取数据库连接

1
2
String url = "jdbc:mysql://localhost:3306/testdb";    
Connection conn = java.sql.DriverManager.getConnection(url, "name", "password");

getConnection()继续调用了重载的getConnection方法

1
2
3
4
5
public static Connection getConnection(String url,
java.util.Properties info) throws SQLException {

return (getConnection(url, info, Reflection.getCallerClass()));
}


重载的方法,省略若干代码,看注释可以知道此时得到的callerCL为null,那么就会执行Thread.currentThread().getContextClassLoader()并赋值给callerCL,而且后面调用isDriverAllowed(aDriver.driver, callerCL)就是使用此时获取的类加载器进行类的加载。于是重点就是停留在Thread.currentThread().getContextClassLoader()。



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();
}
}

...
...
...

for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
...
...
...
} else {
...
...
...
}
}
...
...
...
}

private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
boolean result = false;
if(driver != null) {
Class<?> aClass = null;
try {
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
} catch (Exception ex) {
result = false;
}

result = ( aClass == driver.getClass() ) ? true : false;
}

return result;
}

public ClassLoader getContextClassLoader() {
if (contextClassLoader == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(contextClassLoader,
Reflection.getCallerClass());
}
return contextClassLoader;
}


看一下getContextClassLoader方法,发现仅仅只是简单的校验就获取了。我们回到一开始的java.sql.DriverManager.getConnection(url, “name”, “password”),这里的DriverManager,我们看一下它的初始化



private DriverManager(){}

static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {

...
...
...

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);
}
}
}

public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}

构造方法被private修饰阻止了实例化,那么看一下静态代码块的loadInitialDrivers方法,省略若干代码,我们看一下ClassLoader.getSystemClassLoader(),发现了initSystemClassLoader(),继续往下看



private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
// wrap the exception
throw new Error(oops);
}
}
}
sclSet = true;
}
}

public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}

try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}

Thread.currentThread().setContextClassLoader(this.loader);

...
...
...

}

这里就是实现的关键,注意initSystemClassLoader的以下代码段



sun.misc.Launcher l = sun.misc.Launcher.getLauncher();

scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}

看一下getLauncher()



public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}

try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}

Thread.currentThread().setContextClassLoader(this.loader);

...
...
...

}


发现这里先获取了当前的系统加载器,然后将系统加载器,然后将系统加载器作为线程上下文加载器的父类,相当于构造链表一样。我们再看一下new SystemClassLoaderAction(scl),发现这个类居然有个run方法,而且是使用java.system.class.loader作为实现的线程上下文加载器。



SystemClassLoaderAction(ClassLoader parent) {
this.parent = parent;
}

public ClassLoader run() throws Exception {
String cls = System.getProperty("java.system.class.loader");
if (cls == null) {
return parent;
}

Constructor<?> ctor = Class.forName(cls, true, parent)
.getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
ClassLoader sys = (ClassLoader) ctor.newInstance(
new Object[] { parent });
Thread.currentThread().setContextClassLoader(sys);
return sys;
}

到这里我们可以得出结论:对于SPI,通过配置文件java.system.class.loader的方式实现了额外的线程上下文加载器并用于加载SPI组件,于是就打破了双亲委派模型。其实我们平时实现自定义类加载器的方式也是打破双亲委派模型,我们通过重写findClass(1.2之前是通过重写loadClass)阻止使用双亲委派模型。

ps:为什么要使用自定义类加载器?因为系统类加载器只会加载指定目录下的class文件,想加载自己的文件就可以通过自定义ClassLoader,可以用于文件改动后不想重启Java程序的热加载。