一文帮你整明白ContextClassL

ClassLoader的坑爹特性-可见性

开篇,我们先讲一下ClassLoader的坑爹特性-可见性,即:父级ClassLoader加载的类对子级ClassLoader可见,反之亦然。

接下来我们就对这一坑爹特性做一个验证,我们新建Java项目,项目中只有一个Test类,Test类有两个方法call1和call2:

call1有两个参数-完整的className和一个ClassLoader。call2有一个参数-完整的className。我们使用这两个方法进行验证。把该Java项目达成jar包,放到%JAVA_HOME%/jre/lib/ext目录下。这样当我们启动项目时test.jar就会被ExtClassLoader加载。

新建一个项目,并创建一个Girl类,该类只有一个say方法,用来打印“帅哥,你好”。接下来我们使用Test类的call1方法,使用当前类的CassLoader对Girl类进行加载,并创建一个实例,调用say()方法。

从控制打印的信息可以看出,Test类确实是被ExtClassLoader加载的。使用当前类的ClassLoader去创建Gril实例也是没问题的。因为当前类App和Girll都是被AppClassLoader所加载的。

接下来,我们继续调用call1方法,不过这次call1的第二个参数我们不传,即Test类使用加载它的ExtClassLoader去加载并创建Girl实例:

当我们执行代码,控制台就可以看到ClassNotFoundExcption,这个完全符合ClassLoader的坑爹特性-父级ExtClassLoader对子AppClassLoader的加载类是不可见的。

ContextClassLoader

Thread.currentThread().getContextClassLoader(),即获取当前执行线程的ClassLoader。我们调用call2方法:

call2中使用的Thread.currentThread().getContextClassLoader(),该方法获了加载App类的AppClassloader。所以这段代码可以正常运行。

由此可见ContextClassLoader解决了ClassLoader的坑爹特性-可见性。如果你还不清楚ClassLoader的双亲委托模式,可以看一下文章。

讲了这么多,你可能好奇这玩意到底有么斯用?接下来我们通过调试Mysql的驱动,来进一步了解ContextClassLoader的用处。

SPI

Java提供了很多服务提供者接口(ServiceProvideInterface,SPI),允许第三方为这些接口提供实现。常见的有JDBC、JNDI、JAXP、JBI等。

这些SPI的接口由Java核心库提供,而这些SPI的实现则是作为Java的依赖jar包被包含到ClALLPATH里。SPI接口中的代码经常需要加载第三方提供的具体实现。

有趣的是,SPI接口是Java核心库,它是由BootstrapClassLoader来加载。SPI的实现类则是由AppClassLoader来加载的。依照双亲委派模型和可见性,BootstrapClassLoader是无法获取到AppClassLoader加载的类,并不是只有人会坑爹。

JDBC

接下来,让我们来看一段熟悉又陌生的代码,我们使用JDBC连接Mysql数据库,Mysql和Oracle的驱动包我已提前放到lib中了,这里我们只使用Mysql,毕竟装一个Mysql不到10分钟,装个Oracle就......

很显然,代码是正常运行的,并打印DriverManager是由BootstrapClassLoader来加载的。

PS:如果不想看下面这段臭长的文字,可以直接放到结尾看流程。

这里我们并没有显式的指定使用Mysql的驱动,而且我也同时放了Oracle的驱动包。能够正常运行肯定是DriverManager搞事情了,我们查看一下DriverManager的源码:

查看DriverManager的源码,我们发现它有一个static代码块,执行了一个loadInitialDrivers(),通过方法名和注释(通过检查系统属性-jdbc.properties加载最初的JDBC驱动,然后再同通过ServiceLoader机制加载)我们可以看出就是这里搞事情了。

接下来我们就调试一个这个方法:

由于我们并没有设置系统变量“jdbc.drivers”,所以loadInitialDrivers()方法中,我们需要重点


转载请注明:http://www.aierlanlan.com/rzfs/3354.html