《深入理解java虚拟机》一书中谈到破坏类加载器的双亲委派机制时,谈到使用线程上下文类加载器可以破坏该机制,使程序逆向使用类加载器。
那什么时候需要破坏双亲委派机制呢?其中一种情况是java提供的服务提供者接口(Service Provider Interface,SPI)。这些SPI的借口由java核心库提供,加载它们的只能是BootstrapClassLoader。这些接口的实现多由第三方提供,实现的jar包一般放置在classpath中由AppClassLoader加载。这些都不是问题,问题是核心库中的类有可能需要调用到SPI实现类的方法,java中方法体内的局域变量的加载是由方法调用者的加载类来完成,意思是核心库中的类方法中调用第三方实现的类方法时是由BootstrapClassLoader来加载这个第三方实现类的,很明显BootstrapClassLoader是加载不到classpath的类,而双亲委派机制只能向上委派不能向下委派给AppClassLoader,因此这时候就需要破坏这个委派机制了。
网上想找个相关的例子,实在找不到好的。。(如果有合适的一定请告诉我),只能看到一个jdbc的例子,但是我个人认为jdbc的SPI并没有破坏双亲委派机制,该文章地址http://blog.csdn.net/yangcheng33/article/details/52631940。
现在我来分析一下该例子,并谈谈我自己对使用上下文类加载器破坏双亲委派机制的理解
该文以mysql为例
//初始化驱动类 Class.forName("com.mysql.jdbc.Driver").getInstance(); //获得数据库连接 Connection conn = java.sql.DriverManager.getConnection(url,"name","password");
com.mysql.jdbc.Driver是java.sql.Driver接口的实现类。上段代码第一句就是初始化com.mysql.jdbc.Driver类,该实现类中包含静态代码块:
static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
首先Class.forName段的代码是我们自己写的不出意外由AppClassLoader加载,则new Driver()这里的com.mysql.jdbc.Driver也是由AppClassLoader加载,registerDriver方法就是将参数存入DriverManager的CopyOnWriteArrayList类的类参数中。
getConnection方法的内容大致为
private static Connection getConnection( String url, java.util.Properties info, Class<?> caller) throws SQLException { /**传入的caller由Reflection.getCallerClass()得到,该方法 * 可获取到调用本方法的Class类,这儿调用者是java.sql.DriverManager(位于/lib/rt.jar中), * 也就是说caller.getClassLoader()本应得到Bootstrap启动类加载器 * 但是在上一篇文章中讲到过启动类加载器无法被程序获取,所以只会得到null */ ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; synchronized(DriverManager.class) { // callerCL得实际类型变为线程上下文类加载器实例 if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } } if(url == null) { throw new SQLException("The url cannot be null", "08001"); } SQLException reason = null; for(DriverInfo aDriver : registeredDrivers) { // 查看源码可以看出isDriverAllowed的作用是辨别aDriver.driver是否由callerCL来加载 if(isDriverAllowed(aDriver.driver, callerCL)) { try { println(" trying " + aDriver.driver.getClass().getName()); // 调用com.mysql.jdbc.Driver.connect方法获取连接 Connection con = aDriver.driver.connect(url, info); if (con != null) { // Success! return (con); } //后面代码不用看了 } catch (SQLException ex) { if (reason == null) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } } throw new SQLException("No suitable driver found for "+ url, "08001"); }
可以看出这里实现所谓的核心类调用第三方实现类的方法,不过是将com.msyql.jdbc.Driver的实例存入核心类库中的DriverManager,而DriverManager.getConnection方法只是将该实例拿出来用而已,并没有重新初始化一个实例。这里的线程上下文类加载器只是起一个验证的作用,而不是该问作者所谓的“完全破坏了双亲委派模式”
接下来说说我对线程上下文类加载器破坏双亲委派的实现的一些猜想(个人猜测,没找到具体案例解释,书中也没详细讲)。
我想关键在于Class.forName(className,classLoader)这个方法,核心类库中的类想调用classpath中的类可以通过该方法指定类的加载器,这里就可以通过上下文加载器获得AppClassLoader来加载该类了。其实也可以通过ClassLoader.getSystemClassLoader来获取AppClassLoader,只是我们能修改上下文类加载器不能修改系统类加载器。