关于类加载机制推荐这篇:深入探讨Java类加载机制
关于双亲委派模式的破坏,如Tomcat的类加载机制
关于JAVA的SPI机制,Java-SPI
JDBC之所以要破坏双亲委派模式是因为,JDBC的核心在rt.jar中由启动类加载器加载,而其实现则在各厂商实现的的jar包中,根据类加载机制,若A类调用B类,则B类由A类的加载器加载,也就是说启动类加载器要加载jar包下的类,我们都知道这是不可能的,启动类加载器负责加载$JAVA_HOME中jre/lib/rt.jar
里所有的class,那么JDBC是如何加载这些Driver
实现类的?
通过Thread.currentThread().getContextClassLoader()
得到线程上下文加载器来加载Driver实现类。
先来看一个例子:
mysql
mysql-connector-java
5.1.45
public static void main(String[] args)
{
Enumeration<Driver> drivers = DriverManager.getDrivers();
Driver driver;
while (drivers.hasMoreElements())
{
driver = drivers.nextElement();
System.out.println(driver.getClass() + "------" + driver.getClass().getClassLoader());
}
System.out.println(DriverManager.class.getClassLoader());
}
输出结果如下:
class com.mysql.jdbc.Driver-----sun.misc.Launcher$AppClassLoader@18b4aac2
class com.mysql.fabric.jdbc.FabricMySQLDriver-----sun.misc.Launcher$AppClassLoader@18b4aac2
DriverManager classLoader:null
可以看到代码中并没有调用 Class.forName(“”)的代码,但DriverManager中已经加载了两个 jdbc 驱动,而却这两个驱动都是使用的应用类加载器(AppClassLoader)加载的,而DriverManager本身的类加载器确是 null 即BootstrapClassLoader,按照双亲委派模型的规则,委派链如下:
SystemApp class loader -> Extension class loader -> Bootstrap class loader
,父加载器BootstrapClassLoader是无法找到AppClassLoader加载的类的
查看DriverManager
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
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;
}
});
......
关于AccessController的doPrivileged使用
调用了ServiceLoader.load得到ServiceLoader对象,该类是JAVA的SPI机制的实现,内部实现了一个迭代器,循环调用其next方法,该方法里调用Class.forName
加载各厂商的Driver类,如com.mysql.jdbc.Driver。
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
这里获得线程上下文的ClassLoader(关于getContextClassLoader,解释在文章最后),最终将被Class.forName使用,用该加载器来加载在我们项目下的com.mysql.jdbc.Driver。
创建了ServiceLoader对象,将得到的ClassLoader
与Driver.class
传进去
public final class ServiceLoader<S>
implements Iterable<S>
{
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();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
reload
方法创建了一个LazyIterator
,来看看它的hasNext
与next
方法
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);
}
}
hasNext
调用了hasNextService
private static final String PREFIX = "META-INF/services/";
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//fullName = META-INF/services/java.sql.Driver
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;
}
该方法就是获取META-INF/services/java.sql.Driver
中下一个Driver的全限定名。以mysql-connector为例:
在上面例子中输出结果就是这两个Driver。
next
方法就是调用Class.forName
通过hasNext
里得到的nextName
与传过来的ClassLoader
加载Driver类。
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);
}
}
调用nextService
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
......
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
加载并初始化了Driver类。
JDBC通过Thread.currentThread().getContextClassLoader()
得到线程上下文加载器来加载Driver实现类。
关于getContextClassLoader:
线程上下文类加载器是从jdk1.2开始引入的,类Thread中的getContextClassLoader()
与setContextClassLoader(ClassLoader c1)
,分别用来获取和设置类加载器。
如果没有通过setContextClassLoader
方法进行设置的话,线程将继承其父线程的上下文加载器,java应用运行时的初始线程的上下文类加载器是系统类加载器(这里是由Launcher类设置的)。在线程中运行的代码可以通过该类加载器来加载类和资源。
SPI(Service Provider Interface,服务提供者接口,指的是JDK提供标准接口,具体实现由厂商决定。例如sql),如上面的JDBC
父ClassLoader可以使用当前线程Thread.current.currentThread().getContextClassLoader()
所指定的classLoader加载的类。这就改变了父ClassLoader不能使用子ClassLoader加载的类的情况,即改变了双亲委托模型。
线程上下文类加载器就是当前线程的CurrentClassloader。
在双亲委托模型下,类加载器是由下至上的,即下层的类加载器会委托上层进行加载。但是对于SPI来说,有些接口是JAVA核心库提供的,而JAVA核心库是由启动类加载器来加载的,而这些接口的实现却来自于不同的jar包(厂商提供),JAVA的启动类加载器是不会加载其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求。而通过给当前线程设置上下文类加载器,就可以设置的上下文类加载器来实现对于接口实现类的加载