美文网首页
类加载5 线程上下文类加载器

类加载5 线程上下文类加载器

作者: 得力小泡泡 | 来源:发表于2020-11-26 23:37 被阅读0次

例子

public class MyTest4 {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getContextClassLoader());
        System.out.println(Thread.class.getClassLoader());
    }
}

输出

sun.misc.Launcher$AppClassLoader@18b4aac2
null

理由:
1、当前线程的上下文类加载器是应用类加载器
2、Thread是JDK中的系统类是由启动类加载器加载

正式介绍线程的上下文类加载器之前需要介绍一些理论性的东东
  • 当前类加载器(Current ClassLoader):每一个类都会使用自己的类加载器(既加载自身的类加载器)来去加载其它类(指的是所依赖的类),如果ClassX引用了ClassY,那么ClassX的类加载器就会加载ClassY(前提是ClassY尚未被加载)。
  • 线程上下文类加载器(Context ClassLoader):线程上下文类加载器是从JDK1.2开始引入的,类Thread中的getContextClassLoader()setContextClassLoader(ClassLoader cl)分别用来获取和设置上下文类加载器。如果没有通过setContextClassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器。Java应用运行时初始线程的上下文类加载器是系统类加载器【所以这也是为啥上面的第一行输出是系统类加载器的原因之所在】。在线程中运行的代码可以通过该类加载器来加载类与资源。
线程上下文类加载器的重要性

我们在使用JDBC操作数据库时会如下进行编写:

Class.forName("com.mysql.driver.Driver");

Connection conn = Driver.getConnection();

Statement st = conn.getStatement();

JDBC是一个标准,这就说明使用到的Connection和Statement都是内置在JDK当中的标准,都是抽象接口,而且是位于rt.jar中,其实现肯定是由不同的数据库厂商来实现,那么问题就来了:这些标准都是由根类加载器所加载的,但是具体的实现是由具体的厂商来做的,那肯定是需要将厂商的jar放到工程的classpath当中来进行使用,很显然厂商的这些类是没办法由启动类加载器去加载,会由应用类加载器去加载,而根据“父类加载器所加载的类或接口是看不到子类加载器所加载的类或接口,而子类加载器所加载的类或接口是能够看到父类加载器加载的类或接口的”这一原则,那么会导致这样一个局面:比如说java.sql包下面的某个类会由启动类加载器去加载,该类有可能会要访问具体的实现类,但具体实现类是由应用类加载器所加载的,java.sql类加载器是根据看不到具体实现类加载器所加载的类的,这就是基于双亲委托模型所出现的一个非常致命的问题,这种问题不仅是在JDBC中会出现,在JNDI、xml解析等SPI(Service Provider Interface)场景下都会出现的
所以这里总结一下:父ClassLoader可以使用当前线程Thread.currentThread().getContextLoader()所指定的ClassLoader加载的类,这就改变了父ClassLoader不能使用子ClassLoader或者其它没有直接父子关系的ClassLoader加载的类的情况,既改变了双亲委托模型。线程上下文类加载器就是当前线程的Current ClassLoader。在双亲委托模型下,类加载是由下至上的,既下层的类加载器会委托上层进行加载。但是对于SPI来说,有些接口是Java核心库所提供的,而Java核心库是由启动类加载器来加载的,而这些接口的实现去来自于不同的jar包(厂商提供)。Java的启动类加载器是不会加载其它来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求。而通过给当前线程设置上下文类加载器,就可以由设置的上下文类加载器来实现对于接口实现类的加载。

下面以JDBC的这种SPI场景用图来更具体的描述一下:
image.png

很明显JDBC会去引用JDBCImpl的具体厂商的实现,而JDBC标准是由根类加载器所加载,那对于具体实现厂商的类也会用根类加载器去加载,而由于它们是处于工程中的classPath当中,由系统类加载器去加载,很显然是没办法由根类加载器去加载的,为了解决这个问题,线程的上下文类加载器就发挥作用了。

下面举一些示例代码来对它有进一步的认识:
例子

package com.test;


public class MyTest4 implements Runnable{

    private Thread thread;

    public MyTest4() {
        thread = new Thread(this);
        thread.start();
    }

    @Override
    public void run() {
        ClassLoader classLoader = this.thread.getContextClassLoader();

        this.thread.setContextClassLoader(classLoader);

        System.out.println("ClassL:" + classLoader.getClass());
        System.out.println("Parent:" + classLoader.getParent().getClass());
    }

    public static void main(String[] args){
        new MyTest4();
    }
}

输出

ClassL:class sun.misc.Launcher$AppClassLoader
Parent:class sun.misc.Launcher$ExtClassLoader

分析:
由上面的理论可知:Java应用运行时初始线程的上下文类加载器是系统类加载器

那思考一下:为什么默认的线程上下文类加载器就是系统类加载器呢?肯定是在某个地方给设置了,其实它是在Launcher中进行设置的,如下:
image.png
1、线程上下文类加载器的一般使用模式(获取 - 使用 - 换原)
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();//获取
        try{
            ClassLoader targetTccl = xxx;//要设置的上下文类记载器
            Thread.currentThread().setContextClassLoader(targetTccl);//设置
            myMethod();//使用
        } finally {
            Thread.currentThread().setContextClassLoader(classLoader);//还原
        }

其由myMethod()里面则调用了Thread.currentThread().getContextClassLoader()获取当前线程的上下文类加载器做某些事情。

2、如果一个类由类加载器A加载,那么这个类的依赖类也是由相同的类加载器加载的(如果该依赖类之前没有被加载过的话),ContextClassLoader的作用就是为破坏Java的类加载委托机制。
3、当高层提供了统一的接口让低层来实现,同时又要在高层加载(或实例化)低层的类时,就必须要通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。

ServiceLoader在SPI中的重要作用分析

源码分析:https://www.cnblogs.com/webor2006/p/9260949.html

SPI的规范,去特定的目录下寻找特定的文件,解析出来文件的每一行,并且把每一行的信息读取过来,需要的时候进行加载

这次举一个具体的例子来进一步的加深对线程上下文类加载器的理解,这里以mysql为例,当然得要将mysql的依赖jar给导进来喽,有如下代码

java.sql.Driver(有两个驱动)

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

示例代码

package com.test;

import java.sql.Driver;
import java.util.Iterator;
import java.util.ServiceLoader;

public class MyTest5 {
    public static void main(String[] args) {
        ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
        Iterator<Driver> iterator = loader.iterator();

        while(iterator.hasNext())
        {
            Driver driver = iterator.next();
            System.out.println("dirver: " + driver.getClass() + ", loader:" + driver.getClass().getClassLoader());
        }

        System.out.println("当前线程上下文类加载器:" + Thread.currentThread().getContextClassLoader());
        System.out.println("ServiceLoader的类加载器:" + ServiceLoader.class.getClassLoader());
    }
}

输出:


image.png

对示例代码进行分析
1、ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
创建一个新的服务加载器为给定的服务类型,使用当前线程的上下文类加载器去加载具体的驱动(最核心的一步,如果没有它是不可能正常的能加载到具体的驱动类的,这又进一步又看到线程上下文类加载器的重要性!!
源码实现:

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

首先获取的是当前线程上下文类加载器,然后再去调用它的带有两个参数的重载方法,在继续往下跟进之前先来挼一下获取线程上下文类加载器的一个意图,MyTest5具体是由系统类加载器加载,ServiceLoader肯定是先由系统类去加载,而根据双亲委托最终会委托给根类加载器去加载,好!此时再到load()方法里面,默认当然也是由根类加载器去加载驱动喽,但是很明显具体的驱动都是放在classpath当中很明显是没法由根类加载器加载的,所以这里获取线程上下文类加载器,而默认它就是系统类加载器嘛,所以改变了委托的策略,最终具体驱动就可以用系统类去加载的,这也是线程上下文类加载器的使用场景之所在,继续往下分析:
直接构建一个ServiceLoader对象

    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

继续深进
(1)对于传进的svc参数,一定不能为null
(2)如果cl不为null,用cl的类加载器;如果cl为null,用系统类加载器
(3)acc是安全问题,忽略
(4)调用reload()方法

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

继续深入reload()方法
(1)清空缓存(已经加装的服务提供者)
(2)生成一个待延迟的迭代器

    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;

    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

继续深入 LazyIterator类
LazyIterator是ServiceLoader类一个内部类,用来实现完全延迟提供者查找

    // Private inner class implementing fully-lazy provider lookup
    //
    private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;
        //将两个参数进行实例化
        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

也就是这个ServiceLoader.load()方法就是初始化了一个延迟迭待的对象

2、Iterator<Driver> iterator = loader.iterator();
延迟加载这个loader的服务的可用提供者
(1)直接构建一个迭代器
(2)先缓存中获取迭代器对象
(3)hasNext()方法和next()方法最终调用了之前创建延迟的迭代器对象

    public Iterator<S> iterator() {
        return new Iterator<S>() {
            
            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

3、while(iterator.hasNext())

image.png
image.png
image.png
其中的PREFIX之前也看到过,如下:
image.png
4、Driver driver = iterator.next();
image.png
image.png
image.png

相关文章

网友评论

      本文标题:类加载5 线程上下文类加载器

      本文链接:https://www.haomeiwen.com/subject/mjnoiktx.html