阅读本篇文章前默认已经掌握了Java的双亲委派模型。
最近在看Tomcat的自定义类加载器,在Tomat中一个JVM环境可能运行多个web application,为了对web application进行环境隔离,比如每个web application可以加载同名Servlet,为每一个Context定义了自己的类加载器,打破了Java类加载的双亲委派模型。
另外一个违背了Java类加载双亲委派模型的例子是Java的SPI机制。所谓Java SPI指的就是框架的开发者提供接口,然后服务的提供者提供接口的实现,调用者调用接口,如下图所示
Java SPI一个典型的例子就是JDBC接口java.sql.Driver
的实现。Java提供了一个接口,数据库的服务商提供其实现,比如MySQL,PostgreSQL等等。
其实现机制是在项目工程下创建META-INF/services目录,然后创建java.sql.Driver
文件,在该文件内提供实现了java.sql.Driver
接口的文件的全类名,比如在mysql-connector-java-8.0.28.jar
中有META-INF/services/java.sql.Driver
文件,该文件的内容为
com.mysql.cj.jdbc.Driver
根据该路径,我们发现该类的确实现了java.sql.Driver接口
package com.mysql.cj.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
在Java中,我们通常通过以下方式得到JDBC连接
String url = "jdbc:xxxx://xxxx:xxxx/xxxx";
Connection conn = DriverManager.getConnection(url,username,password);
.....
我们追踪到DriverManager
这个类中,该类有一个静态代码块,在调用该类的静态方法的时候会被调用
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
根据方法名可知,jdbc的driver在该静态代码块中被加载。**但是这里会有一个疑问,DriverManager是JAVA_HOME/rt.jar包中的类,因此该类的类加载器应该是最上层的Bootstrap ClassLoader。然而java.sql.Driver
的各个实现类是无法写到rt.jar路径下,那又如何在DriverManager
类中去加载服务商提供的各个实现呢?**这里就打破了双亲委派模型,用到了上下文类加载器。在该类的loadInitialDrivers
方法中有这样一段代码,如下所示:
private static void loadInitialDrivers() {
...
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
是SPI类加载器,其load方法中会拿到线程上下文类加载器,该类加载器就是AppClassLoader
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
继续追踪代码,在ServiceLoader#load(Class<S> service, ClassLoader loader)
中初始化了成员属性
// The class or interface representing the service being loaded
this.service = Objects.requireNonNull(svc, "Service interface cannot be null");
// The class loader used to locate, load, and instantiate providers
this.loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
// The access control context taken when the ServiceLoader is created
this.acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
// Cached providers, in instantiation order
this.providers.clear();
// The current lazy-lookup iterator
this.lookupIterator = new LazyIterator(service, loader);
回到DriverManager#loadInitialDrivers
方法中,其调用了ServiceLoader#iterator
方法返回了一个迭代器,在该迭代器中,会先在providers
中看是否缓存了类,如果没有则调用lookupIterator.hasNext
和lookupIterator.next
(正是因为这个原因所以称为懒加载迭代器吧)
/**
* 获取已加载服务迭代器,默认为空,通过hasNext方法加载
*/
public Iterator<S> iterator() {
return new Iterator<S>() {
//获取当前已加载服务迭代器
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
// 重写Iterator.hasNext方法
// 判断已加载迭代器中是否存在下一个,存在则返回true
// 否则返回待加载迭代器中是否存在下一个
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
// 重写Iterator.next方法
// 判断已加载迭代器中是否存在下一个,存在则返回这个实现类
// 否则返回待加载迭代器中的下一个,为空抛出异常
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
//重写方法,不允许溢出
public void remove() {
throw new UnsupportedOperationException();
}
};
}
private class LazyIterator implements Iterator<S>{
// 提供服务类 即 要加载的接口
Class<S> service;
// 类加载器
ClassLoader loader;
// META-INF/services/java.sql.Driver文件所在的位置URL
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 {
// 服务接口类地址
// META-INF/services/java.sql.Driver
String fullName = this.PREFIX + this.service.getName();
// 拿到META-INF/services/java.sql.Driver文件的所有地址URL
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;
}
// 解析java.sql.Driver文件,拿到里面的全类名
pending = parse(service, configs.nextElement());
}
// 将全类名赋值给nextName
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);
}
}
}
经过以上讲解,应该清晰的知道了为什么Java SPI需要打破双亲委派模型,以及Java SPI如何使用线程上下文类加载器去加载类。
下面是自己写的一个例子,spi-demo。