双亲委派模型的破坏者-线程上下文类加载器

在Java应用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口允许第三方为他们提供实现,如常见的SPI有JDBC、JNDI等,这些SPI的接口属于Java核心库,一般存在rt.jar包中,由bootstrap类加载器加载,而SPI的第三方实现代码则是作为Java应用所依赖的jar包被存放在classpath路径下,由于SPI接口中的代码经常需要加载具体的第三方实现类并调用其相关方法,但SPI的核心接口类是由Bootstrap类加载器来加载的,而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派机制的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。在这种情况下,我们需要一种特殊的类加载器来加载第三方的类库,而线程上下文类加载器就是很好的选择。

线程上下文类加载器(contextClassLoader)是从JDK1.2开始引入的,我们可以通过java.lang.Thread类中的getContextClassLoader()和setContextClassLoader(ClassLoader cl)方法来获取和设置线程的上下文类加载器。如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初试线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的代码可以通过此类加载器来加载类和资源,如下图所示,以jdbc.jar加载为例。
在这里插入图片描述
如图可知rt.jar核心包是有Bootstrap类加载器加载的,其内包含SPI核心接口类,由于SPI中的类经常需要调用外部实现类的方法,而jdbc.jar包含外部实现类(jdbc.jar存在于classpath路径)无法通过Bootstrap类加载器加载,因此只能委派线程上下文加载器的加载方式破坏了“双亲委派模型”,它在执行过程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器,当然这也使得Java类加载器变得更加灵活。为了进一步证实这种场景,不妨看看DriverManager类的源码,DriverManager是Java核心rt.jar包中的类,该类用来管理不同数据库的实现驱动即Driver,它们都实现了Java核心包中的java.sql.Driver接口,如mysql驱动包中的com.mysql.jdbc.Driver,这里主要看看如何加载外部实现类,在DriverManager初始化时会执行如下代码

//DriverManager是Java核心包rt.jar的类 
public class DriverManager {
	//省略不必要的代码 
	static {
		loadInitialDrivers();//执行该方法
        println("JDBC DriverManager initialized");
    }

	//loadInitialDrivers方法
	private static void loadInitialDrivers() {
       	sun.misc.Providers()
       	AccessController.doPrivileged(new PrivilegedAction<Void>() {
			public Void run() {
				//加载外部的Driver的实现类
				ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
				//省略不必要的代码...... 
			}
	}); 
}

在DriverManager类初始化时执行了loadInitialDrivers()方法,在该方法中通过ServiceLoader.load(Driver.class);去加载外部实现的驱动类,ServiceLoader类会去读取mysql的jdbc.jar下META-INF文件的内容,如下所示:
在这里插入图片描述加载meta-inf过程:
实现延迟服务提供者查找
DriverManager.loadInitialDrivers -> ServiceLoader.load -> reload ->
lookupIterator = new LazyInterator(service, loader);
加载meta-inf,初始化驱动
loadedDrivers.iterator() -> driversIterator.hasNext() -> hasNextService ->
ClassLoader.getSystemResources(fullName);

这样ServiceLoader会帮我们处理一切,并最终通过load()方法加载

public static <S> ServiceLoader<S> load(Class<S> service) { 
		//通过线程上下文类加载器加载
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
}

核心包的SPI类对外部实现类的加载都是基于线程上下文类加载器执行的,通过这种方式实现了Java核心代码内部去调用外部实现类。

简而言之就是ContextClassLoader默认存放了AppClassLoader的引用,由于它是在运行时被放在了线程中,所以不管当前程序处于何处(BootstrapClassLoader或者ExtClassLoader等),在任何需要的时候都可以用Thread.currentThread().getContextClassLoader()取出应用程序类加载器来完成需要的操作。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值