线程上下文类加载器
该加载器十分的重要,也十分的优雅。在Tomcat和Spring中有大量的应用。作为补充,它可以补充JDK提供的三种加载器不能实现的功能,使之更为灵活。
双亲委派模型痛点场景:
Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC(Java官方并不提供具体实现,而是由各自的数据库厂商去实现)、JCE、JNDI、JAXP 和 JBI 等。
SPI接口均由Java核心库来提供,而实现代码都为其余厂商提供(一般都在我们引入的第三方jar包里面)。所以问题就来了:SPI接口中的代码经常需要加载具体的实现类,也就是说我再加载JDBC的时候就需要有实现类。 但是SPI接口是Bootstrap Classloader来加载的,而实现类在类路径由AppClassLoader来加载,所以SPI加载的时候铁定就加载不到实现类了。(因为违反了层级委托关系嘛)
解决方案:JDK1.2提供了上下文类加载器来解决此问题。它破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。看了很多博文,我一直都不理解它具体是如何打破“双亲委派模型”呢?知道我看到了JDBC驱动的加载过程,才彻底的了解了里面的原因~
写个案例:
public static void main(String[] args) throws SQLException { //Class.forName("com.mysql.jdbc.Driver"); Connection conn = java.sql.DriverManager.getConnection("jdbc:mysql://localhost:3306/jedi", "name", "password"); System.out.println(conn); //com.mysql.jdbc.JDBC4Connection@15d0c81b }
细心的朋友会发现,我把平时我们认为必须要写的Class.forName("com.mysql.jdbc.Driver");这句代码去掉了,但程序还是能正常运行获取到链接。
这是为什么呢?这是因为从Java1.6开始自带的jdbc4.0版本已支持SPI服务加载机制,只要mysql的jar包在类路径中,就可以注册mysql驱动。
那到底是在哪一步自动注册了mysql driver的呢?重点就在DriverManager.getConnection()中。我们都是知道调用类的静态方法会初始化该类静态代码块,so玄机就在这个DriverManager的静态代码块里。
当然里面玄机还有很多,但核心原理就是利用到了上下文加载器来实现加载,具体各位可以下面博文,它比我说得好~
Java上线文加载器加载JDBC驱动
URLClassLoader
位于java.net包。从JDK源码上来看其实是URLClassLoader继承了ClassLoader,也就是说URLClassLoader把ClassLoader扩展了一下,所以可以理解成URLClassLoader功能要多点。
ClassLoader只能加载classpath下面的类,而URLClassLoader可以加载**任意路径**下的类。他
们的继承关系如下:
public class URLClassLoader extends SecureClassLoader {} public class SecureClassLoader extends ClassLoader {}
URLClassLoader提供了这个功能,它让我们可以通过以下几种方式进行加载:
* 文件: (从文件系统目录加载)
* jar包: (从Jar包进行加载)
* Http: (从远程的Http服务进行加载)
在Java7的Build 48版中,URLClassLoader提供了close()这个方法,可以将打开的资源全部释放掉,这个给开发者节省了大量的时间来精力来处理这方面的问题。
URLClassLoader 是AppClassLoader和ExtClassLoader的父类,它既可以从本地 文件系统获取二进制加载类,也可以从远程主机获取文件来加载类。
URLClassLoader 动态加载远程jar的代码实现:
借助URLClassLoader 来读取外部的jar包内的class文件,参考下面这个链接:
java中使用URLClassLoader访问外部jar包的java类
总结
以上是关于类加载器的一些介绍和工作原理。知道委托、可见性以及单一性原理,这些对于调试类加载器相关问题时至关重要。这些对于Java高级程序员和架构师来说都是必不可少的知识。