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

SPI与线程上下文类加载器

作者: rock_fish | 来源:发表于2019-10-25 17:08 被阅读0次

    所属文集:ClassLoader串烧


    前提

    传送门 :理解当前类加载器,主动加载,自动加载是什么!

    需求

    程序运行过程中要用到的类,通过当前类加载器自动加载,加载不到(不在当前类加载器的类资源管辖范围),如果要使用这个类,必须指定一个能够加载这个类的加载器去加载,而怎么获取这个加载器是个问题。
    程序都是在线程中执行,那么从线程的上下文中去拿最合理,所以就诞生了线程上下文类加载器,这个加载器的是非自动加载,即通过forName 或者 loadClass的方式去加载类。

    两种场景

    1.当高层提供了统一接口,让低层去实现,同时又要是在高层加载(或实例化)低层的类时,必须通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。
    如SPI.下文会从源码验证。

    2.当使用本类 托管类加载,然而加载本类的ClassLoader(当前类加载器)未知时,为了隔离不同的调用者,可以取调用者各自的线程上下文类加载器代为托管执行类加载(指定类加载器)。
    如Spring,看tommcat中如何使用spring加载类!解读这种场景的运用


    概念

    线程的创建者提供了上下文ClassLoader,供加载类和资源时此线程中运行的代码使用。 如果未设置,则默认值为父线程的ClassLoader上下文。 通常将原始线程的上下文ClassLoader设置为用于加载应用程序的类加载器,默认情况下是AppClassLoader;

    获取和设置

    获取
    Thread#getContextClassLoader()
    设置
    Thread#setContextClassLoader()

    经典用法

    线程上下文加载器其实是线程的一个私有数据,跟线程绑定的,这个线程做完启动Context组件的事情后,会被回收到线程池,之后被用来做其他事情,为了不影响其他事情,需要恢复之前的线程上下文加载器。

    线程上下文类加载器(TCCL)的使用方法

    1.获取原TCCL,orign_cl
    try{
    2.指定一个cl,给TCCL
    (ServiceLoader中,使用tccl来加载类)
    } finally{
    3.将TCCL 还原为orign_cl
    }
    
    SPI技术和TCCL

    数据库驱动,java官方核心库定了接口,但是没做实现,三方做了实现;单核心库的代码中要使用三方的实现类.
    技术实现上来说就是ServiceLoader类是由bootstrap(bs)类加载的,但是bs类加载器,加载不到三方实现(classpath路径下)的类,那方法就执行不下去了。而classpath路径下的类,是由AppClassLoader加载的,可以想办法在此时,获取到AppClassLoader,从代码执行流程来看,其实都是线程在承载逻辑执行,提供了贯穿整个逻辑的上下文,可以方便的在这个上下文中设置和获取cl。
    当然线程上下文类加载器可以使用其他的自定义CL
    从ServiceLoader源码中,找到如何使用ThreadContext ClassLoader的;
    通过DriverManager来跟踪调试代码.

    public class SpiDemo {
        public static void main(String[] args) {
                DriverManager.getConnection("");
    }
    

    DriverManager

    static {
            loadInitialDrivers();
            println("JDBC DriverManager initialized");
        }
    
    private static void loadInitialDrivers() {
            String drivers;
            try {
                drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                    public String run() {
                        return System.getProperty("jdbc.drivers");
                    }
                });
            } catch (Exception ex) {
                drivers = null;
            }
            // If the driver is packaged as a Service Provider, load it.
            // Get all the drivers through the classloader
            // exposed as a java.sql.Driver.class service.
            // ServiceLoader.load() replaces the sun.misc.Providers()
    
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                    Iterator<Driver> driversIterator = loadedDrivers.iterator();
    
                    try{
                        while(driversIterator.hasNext()) {
                            driversIterator.next();
                        }
                    } catch(Throwable t) {
                    // Do nothing
                    }
                    return null;
                }
            });
    
            ...
        }
    

    ServiceLoader.load(Driver.class);

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

    new ServiceLoader对象,传入目标类类型,和cl

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

    如果未指定cl,则使用系统类加载器
    看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();
        }
    构造一个迭代器,传入了cl
    public void reload() {
            providers.clear();
            lookupIterator = new LazyIterator(service, loader);
        }
    

    回头再看drivermanager中,加载驱动的代码,从ServiceLoader中,获取一个迭代器,并遍历

                    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                    Iterator<Driver> driversIterator = loadedDrivers.iterator();
                    while(driversIterator.hasNext()) {
                            driversIterator.next();
                        }
    

    loadedDrivers.iterato(); 返回的是 java.util.ServiceLoader#iterator,

    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();
                }
    
            };
        }
    

    看起构造,hasNext() 和 next()方法内部都调用了lookupIterator的hasNext 和 next方法,那么继续看lookupIterator的着两个方法,

    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);
                }
            }
    

    关键点在于hasNextService() nextService()这两个方法

    先看nextService()方法,代码中,

    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
            }
    

    关键点在 Class.forName(cn,false,loader); 最后一个参数loader就是上文中传入的线程上下文类加载器,那么到此处可以明确的知道所谓的SPI 如何使用的线程上下文类加载器进行类加载;进而弄明白为什么线程类加载器,怎么打破双亲委托机制进行了类加载;
    简单的总结;SPI的打破双亲委托机制进行类加载,就是指定类加载器,这个类加载通过线程上下文类加载器来承载(赋值和取出)

    hasNextService()方法

    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 static final String PREFIX = "META-INF/services/";
    所以 fullName : META-INF/services/java.sql.Driver∂
    configs = loader.getResources(fullName);加载资源后,通过
    pending = parse(service, configs.nextElement());解析资源,获取pending的结果
    0 = "com.mysql.jdbc.Driver"
    1 = "com.mysql.fabric.jdbc.FabricMySQLDriver"

    真正理解线程上下文类加载器(多案例分析)

    Java SPI详解

    高级开发必须理解的Java中SPI机制
    Java界最神秘技术ClassLoader,吃透它看这一篇就够了
    走出类加载器的迷宫

    相关文章

      网友评论

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

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