保护私人版权,尊重他人版权。转载请注明出处并附带页面链接
概念
双亲委派模型是指当类加载器收到类加载任务时,会先交由自己的父类加载器去尝试加载,最终会传递到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程序的热加载。