- JVM 在启动时,会将所需 Class 文件加载到内存中去,按需动态加载.
# 前言
- JVM 在启动时,会将所需 Class 文件加载到内存中去,按需动态加载.
# 双亲委托机制
# Java 代码
public class Smile { | |
static { | |
System.out.println("smile static"); | |
} | |
public Smile() { | |
System.out.println("smile init"); | |
} | |
} | |
public class Lxy { | |
static { | |
System.out.println("lxy static"); | |
} | |
public Lxy() { | |
System.out.println("lxy init"); | |
} | |
} | |
public class ClassLoaderTest | |
public static void main(String[] args) { | |
new Smile(); | |
System.out.println("---------------"); | |
new Lxy(); | |
} | |
} |
# jvm 中添加
-verbose:class 或者 -XX:+TraceClassLoading
# 轨迹展示
[Opened /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded java.io.Serializable from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded java.lang.Comparable from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
... | |
[Loaded java.lang.Cloneable from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded java.lang.ClassLoader from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
... | |
[Loaded java.net.URLClassLoader from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded java.net.URL from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded java.util.jar.Manifest from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded sun.misc.Launcher from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded sun.misc.Launcher$AppClassLoader from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded sun.misc.Launcher$ExtClassLoader from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
... | |
[Loaded sun.launcher.LauncherHelper$FXHelper from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded java.lang.Class$MethodArray from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded java.net.AbstractPlainSocketImpl$1 from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded java.lang.Void from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded com.smile.Smile from file:/Users/smile/workSpace/resource/git/temp/smile-company/target/test-classes/] | |
smile static | |
smile init | |
--------------- | |
[Loaded java.net.Inet6Address from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded com.smile.Lxy from file:/Users/smile/workSpace/resource/git/temp/smile-company/target/test-classes/] | |
lxy static | |
lxy init | |
[Loaded java.lang.Shutdown from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded java.lang.Shutdown$Lock from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] | |
[Loaded java.net.Inet6Address$Inet6AddressHolder from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar] |
# 分析
- 从上面轨迹中发现 ClassLoader,Launcher,AppClassLoader,ExtClassLoader 在项目启动时 Jvm 已初始化;
- 类加载器初始化
public class Launcher { | |
private ClassLoader loader; | |
public Launcher() { | |
// 1. 创建 Ext 类加载器 | |
ClassLoader extcl; | |
try { | |
extcl = ExtClassLoader.getExtClassLoader(); // Ext 类加载器 | |
} catch (IOException e) { | |
throw new InternalError( | |
"Could not create extension class loader", e); | |
} | |
// 2. 创建 App 类加载器 (加载器: Ext 类加载器) | |
try { | |
loader = AppClassLoader.getAppClassLoader(extcl); // App 类加载 | |
} catch (IOException e) { | |
throw new InternalError( | |
"Could not create application class loader", e); | |
} | |
// 3. 配置当前线程上下文类加载器为 APPClassLoader | |
Thread.currentThread().setContextClassLoader(loader); | |
// 4. 配置安全管理器 | |
String s = System.getProperty("java.security.manager"); | |
if (s != null) { | |
SecurityManager sm = null; | |
if ("".equals(s) || "default".equals(s)) { | |
sm = new java.lang.SecurityManager(); | |
} else { | |
try { | |
sm = (SecurityManager)loader.loadClass(s).newInstance(); | |
} catch (IllegalAccessException e) { | |
} catch (InstantiationException e) { | |
} catch (ClassNotFoundException e) { | |
} catch (ClassCastException e) { | |
} | |
} | |
if (sm != null) { | |
System.setSecurityManager(sm); | |
} else { | |
throw new InternalError( | |
"Could not create SecurityManager: " + s); | |
} | |
} | |
} | |
static class ExtClassLoader extends URLClassLoader { | |
static { | |
ClassLoader.registerAsParallelCapable(); // ExtClassLoader 注册为可并行加载 | |
} | |
public static ExtClassLoader getExtClassLoader() throws IOException | |
{ | |
final File[] dirs = getExtDirs(); // 拓展文件的加载路径 (Jvm 启动时可通过 '-Djava.ext.dirs' 修改) | |
try { | |
// 以享有 “特权” 的方式执行 run 方法中的代码 | |
return AccessController.doPrivileged( | |
new PrivilegedExceptionAction<ExtClassLoader>() { | |
public ExtClassLoader run() throws IOException { | |
int len = dirs.length; | |
for (int i = 0; i < len; i++) { | |
MetaIndex.registerDirectory(dirs[i]); // 注册路径 (相关方面请看 'MetaIndex' 方面文档解释) | |
} | |
return new ExtClassLoader(dirs); // 创建 ExtClassLoader, 通过 URL 方式加载 | |
} | |
}); | |
} catch (java.security.PrivilegedActionException e) { | |
throw (IOException) e.getException(); | |
} | |
} | |
public ExtClassLoader(File[] dirs) throws IOException { | |
super(getExtURLs(dirs), null, factory); | |
} | |
private static File[] getExtDirs() { | |
String s = System.getProperty("java.ext.dirs"); | |
File[] dirs; | |
if (s != null) { | |
StringTokenizer st = | |
new StringTokenizer(s, File.pathSeparator); | |
int count = st.countTokens(); | |
dirs = new File[count]; | |
for (int i = 0; i < count; i++) { | |
dirs[i] = new File(st.nextToken()); | |
} | |
} else { | |
dirs = new File[0]; | |
} | |
return dirs; | |
} | |
} | |
static class AppClassLoader extends URLClassLoader { | |
static { | |
ClassLoader.registerAsParallelCapable(); // AppClassLoader 注册为可并行加载 | |
} | |
public static ClassLoader getAppClassLoader(final ClassLoader extcl) | |
throws IOException | |
{ | |
final String s = System.getProperty("java.class.path"); // 应用文件的加载路径 (Jvm 启动时,可通过 '-Djava.class.path' 修改) | |
final File[] path = (s == null) ? new File[0] : getClassPath(s); | |
return AccessController.doPrivileged( | |
new PrivilegedAction<AppClassLoader>() { | |
public AppClassLoader run() { | |
URL[] urls = | |
(s == null) ? new URL[0] : pathToURLs(path); | |
return new AppClassLoader(urls, extcl); // 创建 AppClassLoader, 通过 URL 方式加载,父类 'ExtClassLoader' | |
} | |
}); | |
} | |
AppClassLoader(URL[] urls, ClassLoader parent) { | |
super(urls, parent, factory); | |
} | |
} | |
} |
- 从 Launcher 初始化过程看,目前的类加载器已有 ExtClassLoader 和 AppClassLoader, 我们可在 Jvm 启动时通过指定’-Djava.ext.dirs’,’-Djava.class.path’来修改 ExtClassLoader 和 AppClassLoader 加载文件的路径
- 类初始化
public abstract class ClassLoader { | |
public Class<?> loadClass(String name) throws ClassNotFoundException { | |
return loadClass(name, false); | |
} | |
protected Class<?> loadClass(String name, boolean resolve) | |
throws ClassNotFoundException | |
{ | |
synchronized (getClassLoadingLock(name)) { | |
// 1. 查看是否已加载 | |
Class<?> c = findLoadedClass(name); | |
if (c == null) { // 未加载 | |
long t0 = System.nanoTime(); | |
try { | |
if (parent != null) { // 若父类不为 null, 委托父类加载 | |
c = parent.loadClass(name, false); | |
} else { // 父类为 null, 委托 Bootstrap 加载 (JNI) | |
c = findBootstrapClassOrNull(name); | |
} | |
} catch (ClassNotFoundException e) { | |
// ClassNotFoundException thrown if class not found | |
// from the non-null parent class loader | |
} | |
if (c == null) { // 父类中不存在,从当前加载器中寻找 | |
// If still not found, then invoke findClass in order | |
// to find the class. | |
long t1 = System.nanoTime(); | |
c = findClass(name); | |
// this is the defining class loader; record the stats | |
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); | |
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); | |
sun.misc.PerfCounter.getFindClasses().increment(); | |
} | |
} | |
if (resolve) { // 是否解析 | |
resolveClass(c); | |
} | |
return c; | |
} | |
} | |
} |
- 可以看到是先从已检测缓存中获取,有则 [解析] 返回,无则委托父类加载.
![虚拟机-类加载-ExtClassLoader-依赖]()
![虚拟机-类加载-AppClassLoader-依赖]()
- 从依赖图来看,Jvm 默认加载的两个类加载器继承 ClassLoader, parent.loadClass(name, false), 若子类没重写直接调用父类.
public class Launcher { | |
static class AppClassLoader extends URLClassLoader { | |
public Class<?> loadClass(String name, boolean resolve) | |
throws ClassNotFoundException | |
{ | |
int i = name.lastIndexOf('.'); | |
if (i != -1) { | |
SecurityManager sm = System.getSecurityManager(); | |
if (sm != null) { | |
sm.checkPackageAccess(name.substring(0, i)); // 检测文件路径是否允许访问 | |
} | |
} | |
return (super.loadClass(name, resolve)); | |
} | |
} | |
} |
- 若 parent 不存在,则调用 findBootstrapClassOrNull (name), 则说明除 ExtClassLoader 和 AppClassLoader 以外还有一个 BootstrapClassLoader, 那为何初始化时没有看见呢…
public abstract class ClassLoader { | |
private final ClassLoader parent; | |
protected ClassLoader() { | |
this(checkCreateClassLoader(), getSystemClassLoader()); | |
} | |
private ClassLoader(Void unused, ClassLoader parent) { | |
this.parent = parent; | |
if (ParallelLoaders.isRegistered(this.getClass())) { | |
parallelLockMap = new ConcurrentHashMap<>(); | |
package2certs = new ConcurrentHashMap<>(); | |
domains = | |
Collections.synchronizedSet(new HashSet<ProtectionDomain>()); | |
assertionLock = new Object(); | |
} else { | |
// no finer-grained lock; lock on the classloader instance | |
parallelLockMap = null; | |
package2certs = new Hashtable<>(); | |
domains = new HashSet<>(); | |
assertionLock = this; | |
} | |
} | |
@CallerSensitive | |
public static ClassLoader getSystemClassLoader() { | |
initSystemClassLoader(); | |
if (scl == null) { | |
return null; | |
} | |
SecurityManager sm = System.getSecurityManager(); | |
if (sm != null) { | |
checkClassLoaderPermission(scl, Reflection.getCallerClass()); | |
} | |
return scl; | |
} | |
private static synchronized void initSystemClassLoader() { | |
if (!sclSet) { | |
if (scl != null) | |
throw new IllegalStateException("recursive invocation"); | |
sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); | |
if (l != null) { | |
Throwable oops = null; | |
scl = l.getClassLoader(); | |
try { | |
scl = AccessController.doPrivileged( | |
new SystemClassLoaderAction(scl)); | |
} catch (PrivilegedActionException pae) { | |
oops = pae.getCause(); | |
if (oops instanceof InvocationTargetException) { | |
oops = oops.getCause(); | |
} | |
} | |
if (oops != null) { | |
if (oops instanceof Error) { | |
throw (Error) oops; | |
} else { | |
// wrap the exception | |
throw new Error(oops); | |
} | |
} | |
} | |
sclSet = true; | |
} | |
} | |
} |
- 从上所得,BootStrap 是在 ClassLoader 初始化时初始化
public abstract class ClassLoader { | |
private Class<?> findBootstrapClassOrNull(String name) | |
{ | |
if (!checkName(name)) return null; | |
return findBootstrapClass(name); | |
} | |
private native Class<?> findBootstrapClass(String name); | |
} |
- 发现 findBootstrapClass 标有 native, 底层实现使用 C 语言实现的,所有无法看到 java 的实现
- 若委托父类找不到,则尝试从当前类加载器中 findClass(name) 加载
public class URLClassLoader extends SecureClassLoader implements Closeable { | |
private final URLClassPath ucp; | |
protected Class<?> findClass(final String name) | |
throws ClassNotFoundException | |
{ | |
try { | |
return AccessController.doPrivileged( | |
new PrivilegedExceptionAction<Class<?>>() { | |
public Class<?> run() throws ClassNotFoundException { | |
String path = name.replace('.', '/').concat(".class"); // 拼装文件路径 | |
Resource res = ucp.getResource(path, false); // 从当前类加载器负责加载的路径中获取资源 | |
if (res != null) { | |
try { | |
return defineClass(name, res); // 将资源转换为 Class | |
} catch (IOException e) { | |
throw new ClassNotFoundException(name, e); | |
} | |
} else { | |
throw new ClassNotFoundException(name); | |
} | |
} | |
}, acc); | |
} catch (java.security.PrivilegedActionException pae) { | |
throw (ClassNotFoundException) pae.getException(); | |
} | |
} | |
private Class<?> defineClass(String name, Resource res) throws IOException { | |
long t0 = System.nanoTime(); | |
int i = name.lastIndexOf('.'); | |
URL url = res.getCodeSourceURL(); | |
if (i != -1) { | |
String pkgname = name.substring(0, i); | |
// 检测包是否已加载 | |
Manifest man = res.getManifest(); // 编译后类的相关属性 (编译版本号...) | |
if (getAndVerifyPackage(pkgname, man, url) == null) { // 未加载 | |
try { | |
if (man != null) { | |
definePackage(pkgname, man, url); // 定义包 | |
} else { | |
definePackage(pkgname, null, null, null, null, null, null, null); | |
} | |
} catch (IllegalArgumentException iae) { | |
// parallel-capable class loaders: re-verify in case of a | |
// race condition | |
if (getAndVerifyPackage(pkgname, man, url) == null) { | |
// Should never happen | |
throw new AssertionError("Cannot find package " + | |
pkgname); | |
} | |
} | |
} | |
} | |
// Now read the class bytes and define the class | |
java.nio.ByteBuffer bb = res.getByteBuffer(); // 转换为字节 | |
if (bb != null) { | |
// Use (direct) ByteBuffer: | |
CodeSigner[] signers = res.getCodeSigners(); // 资源签名 | |
CodeSource cs = new CodeSource(url, signers); // 代码源 | |
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); // 用来监控加载该资源消耗时间 | |
return defineClass(name, bb, cs); // 转换为 Class | |
} else { | |
byte[] b = res.getBytes(); | |
// must read certificates AFTER reading bytes. | |
CodeSigner[] signers = res.getCodeSigners(); | |
CodeSource cs = new CodeSource(url, signers); | |
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); | |
return defineClass(name, b, 0, b.length, cs); | |
} | |
} | |
} | |
public class URLClassPath { | |
public Resource getResource(String name, boolean check) { | |
if (DEBUG) { | |
System.err.println("URLClassPath.getResource(\"" + name + "\")"); | |
} | |
Loader loader; | |
for (int i = 0; (loader = getLoader(i)) != null; i++) { | |
Resource res = loader.getResource(name, check); | |
if (res != null) { | |
return res; | |
} | |
} | |
return null; | |
} | |
private synchronized Loader getLoader(int index) { | |
if (closed) { | |
return null; | |
} | |
// Expand URL search path until the request can be satisfied | |
// or the URL stack is empty. | |
while (loaders.size() < index + 1) { | |
// Pop the next URL from the URL stack | |
URL url; | |
synchronized (urls) { | |
if (urls.empty()) { | |
return null; | |
} else { | |
url = urls.pop(); | |
} | |
} | |
// Skip this URL if it already has a Loader. (Loader | |
// may be null in the case where URL has not been opened | |
// but is referenced by a JAR index.) | |
String urlNoFragString = URLUtil.urlNoFragString(url); | |
if (lmap.containsKey(urlNoFragString)) { | |
continue; | |
} | |
// Otherwise, create a new Loader for the URL. | |
Loader loader; | |
try { | |
loader = getLoader(url); | |
// If the loader defines a local class path then add the | |
// URLs to the list of URLs to be opened. | |
URL[] urls = loader.getClassPath(); | |
if (urls != null) { | |
push(urls); | |
} | |
} catch (IOException e) { | |
// Silently ignore for now... | |
continue; | |
} | |
// Finally, add the Loader to the search path. | |
loaders.add(loader); | |
lmap.put(urlNoFragString, loader); | |
} | |
return loaders.get(index); | |
} | |
/* | |
* Returns the Loader for the specified base URL. | |
*/ | |
private Loader getLoader(final URL url) throws IOException { | |
try { | |
return java.security.AccessController.doPrivileged( | |
new java.security.PrivilegedExceptionAction<Loader>() { | |
public Loader run() throws IOException { | |
String file = url.getFile(); | |
if (file != null && file.endsWith("/")) { | |
if ("file".equals(url.getProtocol())) { | |
return new FileLoader(url); | |
} else { | |
return new Loader(url); | |
} | |
} else { | |
return new JarLoader(url, jarHandler, lmap); | |
} | |
} | |
}); | |
} catch (java.security.PrivilegedActionException pae) { | |
throw (IOException)pae.getException(); | |
} | |
} | |
static class JarLoader extends Loader { | |
Resource getResource(final String name, boolean check) { | |
if (metaIndex != null) { | |
if (!metaIndex.mayContain(name)) { | |
return null; | |
} | |
} | |
try { | |
ensureOpen(); | |
} catch (IOException e) { | |
throw new InternalError(e); | |
} | |
final JarEntry entry = jar.getJarEntry(name); | |
if (entry != null) | |
return checkResource(name, check, entry); | |
if (index == null) | |
return null; | |
HashSet<String> visited = new HashSet<String>(); | |
return getResource(name, check, visited); | |
} | |
private void ensureOpen() throws IOException { | |
if (jar == null) { | |
try { | |
java.security.AccessController.doPrivileged( | |
new java.security.PrivilegedExceptionAction<Void>() { | |
public Void run() throws IOException { | |
if (DEBUG) { | |
System.err.println("Opening " + csu); | |
Thread.dumpStack(); | |
} | |
jar = getJarFile(csu); | |
index = JarIndex.getJarIndex(jar, metaIndex); | |
if (index != null) { | |
String[] jarfiles = index.getJarFiles(); | |
// Add all the dependent URLs to the lmap so that loaders | |
// will not be created for them by URLClassPath.getLoader(int) | |
// if the same URL occurs later on the main class path. We set | |
// Loader to null here to avoid creating a Loader for each | |
// URL until we actually need to try to load something from them. | |
for(int i = 0; i < jarfiles.length; i++) { | |
try { | |
URL jarURL = new URL(csu, jarfiles[i]); | |
// If a non-null loader already exists, leave it alone. | |
String urlNoFragString = URLUtil.urlNoFragString(jarURL); | |
if (!lmap.containsKey(urlNoFragString)) { | |
lmap.put(urlNoFragString, null); | |
} | |
} catch (MalformedURLException e) { | |
continue; | |
} | |
} | |
} | |
return null; | |
} | |
} | |
); | |
} catch (java.security.PrivilegedActionException pae) { | |
throw (IOException)pae.getException(); | |
} | |
} | |
} | |
.... | |
} | |
} | |
public class SecureClassLoader extends ClassLoader { | |
protected final Class<?> defineClass(String name, | |
byte[] b, int off, int len, | |
CodeSource cs) | |
{ | |
return defineClass(name, b, off, len, getProtectionDomain(cs)); | |
} | |
protected final Class<?> defineClass(String name, byte[] b, int off, int len, | |
ProtectionDomain protectionDomain) | |
throws ClassFormatError | |
{ | |
protectionDomain = preDefineClass(name, protectionDomain); | |
String source = defineClassSourceLocation(protectionDomain); | |
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source); | |
postDefineClass(c, protectionDomain); | |
return c; | |
} | |
} |
# 结论
- 从运行轨迹来看,可以验证 Jvm 是按需加载 Class.
- Jvm 默认的类加载器有 AppClassLoader -> ExtClassLoader -> BootstrapClassLoader
# 破坏双亲委托机制
# 缘由
- 受加载顺序的约束,只要父类中含有,子加载器中的类将无法被加载。而特殊场景则需要子加载器加载
- SPI 机制 (例: JDBC)
- 受加载顺序的影响,保证加载一个相同的 Class 类库,但特殊场景下需要加载不同版本的类库
- (例: Tomcat)
# 案例
# MySQL
- JDBC 的核心在 Jdk 的 rt.jar 中,只是提供了统一的接口,具体实现是各大厂商实现的。若按照双亲委托的顺序,Jvm 是无法加载各大厂商具体实现 jar 的.
- Driver 接口定义在 JDK 中,实现由各个数据库的服务商来提供,DriverManager (JDK 提供)要加载各个实现了 Driver 接口的实现类,然后进行管理. DriverManager 由 BootStrap 类加载器加载,而其实现是由各服务商提供的,这时就需要委托子类来加载 Driver 实现了.
# Java 代码
public class JdbcClassLoaderTest { | |
public static void main(String[] args) { | |
Enumeration<Driver> drivers = DriverManager.getDrivers(); | |
while (drivers.hasMoreElements()) { | |
Driver driver = drivers.nextElement(); | |
System.out.println(String.format("driver: %s, classLoader: %s", driver.getClass(), driver.getClass().getClassLoader().getClass())); | |
} | |
System.err.println(String.format("driver: %s, classLoader: %s", DriverManager.class, | |
Objects.nonNull(DriverManager.class.getClassLoader()) | |
? DriverManager.class.getClassLoader().getClass() | |
: DriverManager.class.getClassLoader())); | |
} | |
} |
# 结果展示
driver: class com.mysql.jdbc.Driver, classLoader: class sun.misc.Launcher$AppClassLoader | |
driver: class com.mysql.fabric.jdbc.FabricMySQLDriver, classLoader: class sun.misc.Launcher$AppClassLoader | |
driver: class com.alibaba.druid.proxy.DruidDriver, classLoader: class sun.misc.Launcher$AppClassLoader | |
driver: class com.alibaba.druid.mock.MockDriver, classLoader: class sun.misc.Launcher$AppClassLoader | |
driver: class java.sql.DriverManager, classLoader: null |
# 分析
public class DriverManager { | |
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>(); | |
static { | |
loadInitialDrivers(); // 1. 初始化 Drivers | |
println("JDBC DriverManager initialized"); | |
} | |
private static void loadInitialDrivers() { | |
String drivers; | |
// 2. Jvm 启动时指定驱动实现 (多个驱动可用 ':' 分隔), 将在下面采用 BootStrapClassLoader 加载. | |
try { | |
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() { | |
public String run() { | |
return System.getProperty("jdbc.drivers"); | |
} | |
}); | |
} catch (Exception ex) { | |
drivers = null; | |
} | |
// 通过 SPI 机制加载 (ServiceLoader.load (Driver.class)) | |
AccessController.doPrivileged(new PrivilegedAction<Void>() { | |
public Void run() { | |
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); // 3. SPI 获取各厂商实现 (懒加载) | |
Iterator<Driver> driversIterator = loadedDrivers.iterator(); // 4. 转换为 Iterator | |
/* Load these drivers, so that they can be instantiated. | |
* It may be the case that the driver class may not be there | |
* i.e. there may be a packaged driver with the service class | |
* as implementation of java.sql.Driver but the actual class | |
* may be missing. In that case a java.util.ServiceConfigurationError | |
* will be thrown at runtime by the VM trying to locate | |
* and load the service. | |
* | |
* Adding a try catch block to catch those runtime errors | |
* if driver not available in classpath but it's | |
* packaged as service and that service is there in classpath. | |
*/ | |
try{ | |
while(driversIterator.hasNext()) { // 5. 是否有元素 (执行 SPI 解析) | |
driversIterator.next(); // 6. 加载文件 (Class.forName) | |
} | |
} catch(Throwable t) { | |
// Do nothing | |
} | |
return null; | |
} | |
}); | |
println("DriverManager.initialize: jdbc.drivers = " + drivers); | |
if (drivers == null || drivers.equals("")) { | |
return; | |
} | |
String[] driversList = drivers.split(":"); | |
println("number of Drivers:" + driversList.length); | |
for (String aDriver : driversList) { | |
try { | |
println("DriverManager.Initialize: loading " + aDriver); | |
Class.forName(aDriver, true, | |
ClassLoader.getSystemClassLoader()); | |
} catch (Exception ex) { | |
println("DriverManager.Initialize: load failed: " + ex); | |
} | |
} | |
} | |
} | |
public final class ServiceLoader<S> implements Iterable<S> { | |
private static final String PREFIX = "META-INF/services/"; | |
// The class or interface representing the service being loaded | |
private final Class<S> service; | |
// 用于加载对象的类加载器 | |
private final ClassLoader loader; | |
// 加载对象时的权限上下文 | |
private final AccessControlContext acc; | |
// 缓存 | |
private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); | |
// 具体实现的迭代器 (懒迭代) | |
private LazyIterator lookupIterator; | |
/** | |
* 使用当前线程类加载器加载具体实现 | |
*/ | |
public static <S> ServiceLoader<S> load(Class<S> service) { | |
ClassLoader cl = Thread.currentThread().getContextClassLoader(); // 3.1 获取当前线程类加载器 | |
return ServiceLoader.load(service, cl); // 3.2 使用当前线程类加载读取具体实现 (这里就是 ** 破坏双亲委托机制 ** 的地方) | |
} | |
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { | |
return new ServiceLoader<>(service, loader); // 3.2.1 创建对象 | |
} | |
private ServiceLoader(Class<S> svc, ClassLoader cl) { | |
service = Objects.requireNonNull(svc, "Service interface cannot be null"); | |
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; // 3.2.1.1 若没有提供类加载器,则使用 BootStrapClassLoader 类加载器 | |
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; // 3.2.1.2 权限上下文 | |
reload(); // 3.2.1.3 刷新缓存,新建迭代器 | |
} | |
public void reload() { | |
providers.clear(); // 3.2.1.3.1 刷新缓存 | |
lookupIterator = new LazyIterator(service, loader); // 3.2.1.3.2 新建迭代器 | |
} | |
/** | |
* 封装迭代器 | |
*/ | |
public Iterator<S> iterator() { | |
return new Iterator<S>() { // 4.1 转换为迭代器 | |
Iterator<Map.Entry<String,S>> knownProviders | |
= providers.entrySet().iterator(); | |
public boolean hasNext() { | |
if (knownProviders.hasNext()) // 5.1 已知提供者是否存在元素 | |
return true; | |
return lookupIterator.hasNext(); // 5.2 懒迭代中是否存在 | |
} | |
public S next() { | |
if (knownProviders.hasNext()) // 6.1 已知提供者是否存在元素 | |
return knownProviders.next().getValue(); | |
return lookupIterator.next(); // 6.2 懒迭代中获取下一个元素 | |
} | |
public void remove() { | |
throw new UnsupportedOperationException(); | |
} | |
}; | |
} | |
private class LazyIterator implements Iterator<S> { | |
Class<S> service; | |
ClassLoader loader; | |
Enumeration<URL> configs = null; | |
Iterator<String> pending = null; | |
String nextName = null; | |
private LazyIterator(Class<S> service, ClassLoader loader) { | |
this.service = service; // 3.2.1.3.2.1 将要加载的接口 | |
this.loader = loader; // 3.2.1.3.2.2 类加载器 | |
} | |
private boolean hasNextService() { | |
if (nextName != null) { // 5.2.2.1 加载实现的接口名称 | |
return true; | |
} | |
if (configs == null) { | |
try { | |
String fullName = PREFIX + service.getName(); // 5.2.2.2 SPI 读取文件的路径 META-INF/services/java.sql.Driver | |
if (loader == null) | |
configs = ClassLoader.getSystemResources(fullName); | |
else | |
configs = loader.getResources(fullName); // 5.2.2.3 类加载器加载资源 ClassLoader#getResources | |
} 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()); // 5.2.2.4 解析 URL 元素获取实现名称 s (com.mysql.jdbc.Driver, com.mysql.fabric.jdbc.FabricMySQLDriver) | |
} | |
nextName = pending.next(); // 5.2.2.5 下个元素名称 | |
return true; | |
} | |
private S nextService() { | |
if (!hasNextService()) // 6.2.2.1 是否存在具体实现 | |
throw new NoSuchElementException(); | |
String cn = nextName; // 6.2.2.2 (5.2.2.5 缓存的数据) | |
nextName = null; | |
Class<?> c = null; | |
try { | |
c = Class.forName(cn, false, loader); // 6.2.2.3 加载实现 | |
} catch (ClassNotFoundException x) { | |
fail(service, | |
"Provider " + cn + " not found"); | |
} | |
if (!service.isAssignableFrom(c)) { // 6.2.2.4 SPI 接口与 加载的类或超类是否相同 | |
fail(service, | |
"Provider " + cn + " not a subtype"); | |
} | |
try { | |
S p = service.cast(c.newInstance()); // 6.2.2.5 强行转换为 SPI 接口 | |
providers.put(cn, p); // 6.2.2.6 缓存 | |
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) { // 5.2.1 权限上下文是否为 null | |
return hasNextService(); // 5.2.2 真正执行 SPI 解析 | |
} else { | |
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { | |
public Boolean run() { return hasNextService(); } | |
}; | |
return AccessController.doPrivileged(action, acc); | |
} | |
} | |
public S next() { | |
if (acc == null) { // 6.2.1 权限上下文是否为 null | |
return nextService(); // 6.2.2 类加载 | |
} else { | |
PrivilegedAction<S> action = new PrivilegedAction<S>() { | |
public S run() { return nextService(); } | |
}; | |
return AccessController.doPrivileged(action, acc); | |
} | |
} | |
public void remove() { | |
throw new UnsupportedOperationException(); | |
} | |
} | |
} |

com.mysql.jdbc.Driver | |
com.mysql.fabric.jdbc.FabricMySQLDriver |
# 结论
- 从 3.2 来看,SPI 机制采用当前线程的类加载器来加载实现,这样就破坏双亲委托机制
# Tomcat
# 说明
- 因本地已没 Tomcat 的源码,看下别人的分享吧…
- Tomcat 的类加载机制
# 引用
- 浅谈双亲委派和破坏双亲委派
- 超详细 java 中的 ClassLoader 详解
- 双亲委派模式破坏 - JDBC

