所属文集: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界最神秘技术ClassLoader,吃透它看这一篇就够了
走出类加载器的迷宫
网友评论