模型图
破坏双亲委派模型有两种方式
一、引入线程上下文类加载器
Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。
这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由引导类加载器来加载的;SPI的实现类是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类。
而线程上下文类加载器破坏了双亲委派模型,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。
刚开始有个疑问就是:既然你是SPI接口,那你提供接口就好了,为什么要加载具体的实现类?
发现JDK中确实有一些这种情况:
1、接口想提供一个默认的实现。
如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory类中的 newInstance()方法用来生成一个新的 DocumentBuilderFactory的实例。这里的实例的真正的类是继承自 javax.xml.parsers.DocumentBuilderFactory,由 SPI 的实现所提供的。如在 Apache Xerces 中,实现的类是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。而问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。
可以发现DocumentBuilderFactoryImpl本来是存在于org.apache.xerces.jaxp这个包里的,是第三方类库,使用的加载器是AppClassLoader,后来JDK8把他移到了rt.jar内,应该也是为了不破坏双亲委派模型。
2、JDBC java.sql.DriverManager统一注册实现类和获取连接
真正理解线程上下文类加载器(多案例分析)
DruidDriver.registerDriver -> DriverManager.registerDriver -> DriverManager.loadInitialDrivers.AccessController.doPrivileged -> ServiceLoader.load -> driversIterator.next
根据JDBC加载源码进行断点发现DriverManager.loadInitialDrivers初始化时,会在DriverManager中加载com.alibaba.druid.proxy.DruidDriver,此时就是利用的线程上下文类加载器来实现
二、自定义ClassLoader
- 如果不想不破坏双亲委派模型,只要去重写findClass方法
- 如果想要去破坏双亲委派模型,需要去重写loadClass方法