SPI与线程上下文类加载器

所属文集: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,吃透它看这一篇就够了
走出类加载器的迷宫



作者:rock_fish
链接:https://www.jianshu.com/p/304cb533ba2d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值