JVM-类加载器源码解析
一、准备工作
先做一下说明,Launcher类是属于 sun.misc
包的,ClassLoader是属于 java.lang
包的。
所以,我们要查看 sun.misc
包下的源码,但是里面都是经过 IDEA反编译之后变量都是 var0,var1这些,需要去open-jdk下载源码:
java
// git地址
git clone https://github.com/openjdk-mirror/jdk.git
// 切换到jdk目录
cd jdk
// 拉去对应自己JDK版本的分支
git checkout jdk8u/jdk8u/master
在当前目录找到:src\share\classes
直接打包这个目录下的文件为 src.zip
替换 jdk 目录下的 src ,然后再重启IDEA就可以查看源码文件啦,其实我们也可以直接用IDE打开 源码工程,方便我们注释。
在进去看源码已经是 .java
文件了 (中文是我自己写了注释之后打包的 ^ ∨ ^)
OK,准备工作完毕!!!
二、类加载器初始化过程
这里还是贴个图吧,双亲委派机制。
先解释一下用到类之间关系:
-
ExtClassLoader(扩展类加载器)、AppClassLoader(系统类加载器)是Launcher的两个内部类,同时也是ClassLoader的两个子类。
-
这里没有 根类加载器,因为根类加载器是C++代码写的,封装在了Java虚拟机中(解释一下)。
-
其实主要就一步调用
initSystemClassLoader()
方法对类加载器进行初始化,然后该方法里面也就一行比较重要的,调用Launcher
类的getLauncher()
方法。 -
initSystemClassLoader()
方法利用单例的DLC方式对类加载进行初始化。javapublic abstract class ClassLoader { /** * 获取系统类加载器 */ @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 if (scl != null) // DLC 双重校验 throw new IllegalStateException("recursive invocation"); //递归调用 ,保证类加载器只初始化一次 // 获取类加载器: // 这里会对 扩展类加载器和系统类加载器进行初始化 // 扩展类加载器会将ClassLoader.parent 设置为根类加载器 // 系统类加载器会将ClassLoader.parent 设置为扩展类加载器 // 这个是核心方法进入看看!!! sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); if (l != null) { Throwable oops = null; scl = l.getClassLoader(); // 这里将返回的系统类加载器 赋值给 scl 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; // 标记为初始化完成。 } } }
来到 sun.misc.Launcher#getLauncher()
,我们可以看到对类加载器初始化操作是在 Launcher
的构造方法中。
主要做了这几件事情:
-
1、创建扩展类加载器
-
2、创建系统类加载器
-
3、将线程上下文加载器默认设置为系统类加载器(破坏双亲委派模型,在第五章介绍)
-
4、对类加载器做些安全性校验(不用关注,非主流程)
javapublic class Launcher { private static URLStreamHandlerFactory factory = new Factory(); private static Launcher launcher = new Launcher(); private static String bootClassPath = System.getProperty("sun.boot.class.path"); // 这个就是根类加载器要加载的路径 public static Launcher getLauncher() { return launcher; } private ClassLoader loader; // 类加载器(可以是扩展加载器,可以是系统类加载器,可以是自定义类加载器) // 初始化 构造方法!!!! public Launcher() { // Create the extension class loader 创建一个扩展加载器 ClassLoader extcl; try { // 1.这里是去创建扩展类加载器,返回扩展类加载器的实例 extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError( "Could not create extension class loader", e); } // Now create the class loader to use to launch the application try { // 2. 创建系统类加载器 // 这里是去创建系统类加载器的实例,这里我们可以看到: // getAppClassLoader(extcl)中的参数extcl是扩展类加载器get得到的结果, // 最终作为参数传递到getAppClassLoader中。 // 并且赋给了loader,而loader又是Launcher类的一个私有成员变量。 // 而且这句话也说明了, 扩展类加载器是系统类加载器的双亲!!! loader = AppClassLoader.getAppClassLoader(extcl); // } catch (IOException e) { throw new InternalError( "Could not create application class loader", e); } // Also set the context class loader for the primordial thread. // 3.为原始线程 设置 线程上下文类加载器,这里默认设置的是 系统类加载器。 Thread.currentThread().setContextClassLoader(loader); // Finally, install a security manager if requested // 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); } } } /* * Returns the class loader used to launch the main application. */ public ClassLoader getClassLoader() { return loader; } }
下面我们主要看看步骤 1、创建扩展类加载器,2、创建系统类加载器
-
1、创建扩展类加载器:
extcl = ExtClassLoader.getExtClassLoader();
创建扩展类加载器主要做了两件事情:
-
①、获取扩展类加载器所能加载的那几个目录路径;
-
②、在调用构造方法的时候,将 父类设置为 null ,即 根类加载器。
javapublic class Launcher { /* * 扩展类加载器 * 这里ExtClassLoader继承了URLClassLoader,URLClassLoader又继承了SecureClassLoader, * SecureClassLoader又继承了ClassLoader */ static class ExtClassLoader extends URLClassLoader { static { ClassLoader.registerAsParallelCapable(); } /** * create an ExtClassLoader. The ExtClassLoader is created * within a context that limits which files it can read */ public static ExtClassLoader getExtClassLoader() throws IOException { final File[] dirs = getExtDirs(); // ①、就是返回扩展类加载器所加载的那几个目录路径 try { // Prior implementations of this doPrivileged() block supplied // aa synthesized ACC via a call to the private method // ExtClassLoader.getContext(). // AccessController.doPrivilege主要是做一个权限的校验 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]); } // 最终返回一个ExtClassLoader return new ExtClassLoader(dirs); } }); } catch (java.security.PrivilegedActionException e) { throw (IOException) e.getException(); } } /* * Creates a new ExtClassLoader for the specified directories. */ public ExtClassLoader(File[] dirs) throws IOException { // ②、这里可以看到 // parent:null, 即:扩展类加载器的父类是 根类加载器null super(getExtURLs(dirs), null, factory); // SharedSecrets 作用是一种在不使用反射的情况下调用另一个包中的实现私有方法的机制 // .initLookupCache(this) 会将 URLClassPath类中的 加载器属性赋值为 ExtClassLoader SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this); } } }
-
-
2、创建系统类加载器:
loader = AppClassLoader.getAppClassLoader(extcl);
创建系统类加载器主要做了三件事情:
-
①、将上一步骤创建的 扩展类加载器
extcl
传递过来; -
②、获取系统类加载器所能加载的那几个目录路径;
-
③、在调用构造方法的时候,将 父类设置为
extcl
,即 扩展加载器。javapublic class Launcher { static class AppClassLoader extends URLClassLoader { static { ClassLoader.registerAsParallelCapable(); } /** * @param extcl ①、这里的参数其实就是 扩展类加载器 */ public static ClassLoader getAppClassLoader(final ClassLoader extcl) throws IOException { final String s = System.getProperty("java.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); // 构建url集合 return new AppClassLoader(urls, extcl); // 创建 系统类加载器返回 } }); } final URLClassPath ucp; /* * 创建 系统类加载器返回 */ AppClassLoader(URL[] urls, ClassLoader parent) { // ③、注意,这里是将 parent(这个是ExtClassLoader),传递给 AppClassLoader的父类 // URLClassLoader // -> SecureClassLoader // -> ClassLoader 最终赋值给ClassLoader中的parent属性 super(urls, parent, factory); // 和扩展类加载器一样,也是会将 URLClassPath类中的 加载器属性赋值为 AppClassLoader ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this); ucp.initLookupCache(this); } } }
-
通过初始化的过程,我们可以了解到:扩展类加载器会把根类加载器设置为parent
,系统类加载器会把扩展类加载器设置为parent
。
也从源码找到三个类加载器加的路径:
三、loadClass 加载类过程
双亲委派机制加载类的过程是:
先检查类有没有被加载,如果没有被加载,则先去父加载器加载,从系统类加载器到扩展类加载器,再到根类加载器 ;如果父加载器没能加载这个类,则调用findClass()
方法加载;findClass()
方法是为了支持我们自定义类加载器的时候保持双亲委派模型重写该方法;当然也可以直接重写 loadClass()
方法实现自定义类加载器。两者区别放在下一个章节介绍。
我们从系统类加载的重写方法开始 sun.misc.Launcher.AppClassLoader#loadClass
介绍。(扩展类加载器没有重写loadClass
方法)
-
sun.misc.Launcher.AppClassLoader#loadClass
方法前两个if
的作用主要就是 检查包访问权限;我们重点看
super.loadClass(name, resolve)
→java.lang.ClassLoader#loadClass(java.lang.String, boolean)
javapublic class Launcher { static class AppClassLoader extends URLClassLoader { /** * Override loadClass so we can checkPackageAccess. * 重写loadClass,以便我们可以检查包访问权限。 */ 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)); } } if (ucp.knownToNotExist(name)) { // 在父类加载器及其本地URLClassPath中找不到给定名称的类。 // 检查该类是否已动态定义;如果是,返回加载的类;否则,跳过父委托并调用findClass查找。 Class<?> c = findLoadedClass(name); // 如果找到名字不为空,尝试加载解析,解析不了直接报错就行。 if (c != null) { if (resolve) { resolveClass(c); } return c; } throw new ClassNotFoundException(name); } // 这里调用的是 ClassLoader类中的loadClass(name, resolve)方法; // 首先,我们当前是在 AppClassLoader(系统类加载器中) // 那么,这里 super.loadClass(name, resolve) // 先走的是 1.findLoadedClass(name)检查该类是否已经由当前类加载器加载了; // 再走的就是 2.1 parent.loadClass(name, false) 扩展类加载器加载的逻辑; // 然后根据类加载器的双亲委派模型,又会递归一下走2.2findBootstrapClassOrNull(name)去请求 根类加载器去加载。 return (super.loadClass(name, resolve)); } } }
-
来到方法:
java.lang.ClassLoader#loadClass(java.lang.String, boolean)
这里就是类加载器双亲委派模型实现的地方;大致流程:
- ①、
1、findLoadedClass(name);
先看看该类是否被当前类加载器(系统类加载器)加载过了;(起始:我们从系统类加载器开始)- 如果加载过直接返回了,没有的话,走②
- ②、
2.1 c = parent.loadClass(name, false);
- 这里的
parent
是扩展类加载器,扩展类加载器没有实现loadClass(java.lang.String, boolean)
方法,递归还是回到该方法中。 - 继续检查
findLoadedClass(name);
该类是否被当前类加载器(扩展类加载器)加载过了;- 如果加载过直接返回了,退出递归,没有的话,走③。
- 这里的
- ③、
2.2 c = findBootstrapClassOrNull(name);
- 这里的
parent
是根类加载器,由根类加载器加载; - 加载成功返回,退出递归返回;如果根类加载器都加载不了,那直接系统保错了。
- 这里的
- ④、
3、c = findClass(name);
- 如果由上面3个加载器都没有加载成功的话,那调用该方法,即使用的是自定义类加载器尝试加载。
javapublic abstract class ClassLoader { /** * 使用指定的二进制名称加载类,此方法的默认实现按以下顺序搜索类: * 1、调用findLoadedClass(String)以检查类是否已加载。 * 2、在父类加载器上调用loadClass方法。如果父级为空,则使用虚拟机内置的类加载器(就是根类加载器)。 * 3、调用findClass(String)方法以查找类 (自定义类加载加载)。 * 如果使用上述步骤找到了该类,并且resolve标志为true,则该方法将在生成的class对象上调用resolveClass(class)方法。 * 鼓励ClassLoader的子类重写findClass(String),而不是此方法。 * [就是在重写类加载器的时候,鼓励用重写findClass(String)方法]。 * 除非被重写,否则此方法在整个类加载过程中同步getClassLoadingLock方法的结果。 * * @param name : 类的二进制名称 * @param resolve : 如果为true,则解析类 */ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // getClassLoadingLock(name): 返回类加载操作的对象锁;为了保证当前要加载的对象只被加载一次。 synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded // 1、调用findLoadedClass(String)以检查类是否已加载。 Class<?> c = findLoadedClass(name); if (c == null) { // 为null的话,说明没有加载,下面if内流程开始加载 long t0 = System.nanoTime(); try { // 2、在父类加载器上调用loadClass方法。如果父级为空,则使用虚拟机内置的类加载器(就是根类加载器)。 // 2.1 如果当前类加载器是:系统类加载器,那么这里parent 就是 扩展类加载器 -> 走 if 逻辑 // 2.2 如果当前类加载器是:扩展类加载器,那么这里parent 就是 null(根类加载器) -> 走 else 逻辑 // // 所以这里就是 双亲委派模型实现的地方 // Ⅰ、先是:当前方法 loadClass(String name, boolean resolve)由 AppClassLoader调用 来到这里 // Ⅱ、parent 是 ExtClassLoader 不为null,就会走 parent.loadClass(name, false); // Ⅲ、由于 parent 是 ExtClassLoader,而ExtClassLoader 没有重写loadClass(String name, boolean resolve)方法 // 所以,还会来到 ClassLoader.loadClass(java.lang.String, boolean)这里,即当前方法 // 此时,ExtClassLoader的父类就是 null(根类加载器) if (parent != null) { c = parent.loadClass(name, false); } else { 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. // 如果仍然找不到,则调用findClass以查找该类。 long t1 = System.nanoTime(); // 3、调用findClass(String)方法以查找类。 // 这里的 findClass(name)方法就是交给 用户自己实现的自定义类加载器所要重写的方法!!! 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; } } }
- ①、
四、自定义类加载器
4.1 重写loadClass方法
还是使用 JVM-类加载机制 这篇文章中用的例子:
我们在重写loadClass
方法的时候,如果不主动调用super.loadClass(name)
相当于破坏了双亲委派机制,因此也是官方不推荐的。
java
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
// 这里通过 重写 ClassLoader的loadClass方法,实现一个自定义类加载器
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1)+".class";
InputStream is = getClass().getResourceAsStream(fileName);
// 这个步骤是一定要有的!!!
// 目的是为了防止:
// 当使用自定义类加载器加载的类依赖 像:java.lang.Object、java.lang.String这类的时候,
// 自定义加载器无法加载这些类。
if (is == null) {
// 这里就要 手动的调用super.loadClass(name)
// 目的就是为了使用 本身的三个类加载器去加载对应路径在的类。
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
// 这里使用自定义类加载器加载jvm.part7.ClassLoaderTest对象
Object obj = myLoader.loadClass("jvm.part7.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println("使用的类加载器:" + obj.getClass().getClassLoader());
// jvm.part7.ClassLoaderTest 这个是由 应用类加载器加载的。
System.out.println(obj instanceof jvm.part7.ClassLoaderTest );
System.out.println("使用的类加载器:" + jvm.part7.ClassLoaderTest.class.getClassLoader());
}
}
执行结果:
class jvm.part7.ClassLoaderTest
使用的类加载器:jvm.part7.ClassLoaderTest$1@4141d797 // 自定义类加载器
false
使用的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2 // 系统类加载器
4.2 重写findClass(name)方法
官方推荐重写findClass(name)
方法。
其实经过上面我们对源码的研究知道:findClass(name)
方法是在双亲委派机制加载不了的时候才会被调用,即我们自定义类加载器需要实现的方法。
下面是自定义类加载重写findClass(name)
方法方法的实现;通过打印结果我们可以发现,虽然我们用自定义类加载器去加载类,但是加载的类由于在当前项目的目录内,因此,还是可以通过 应用类加载器加载该类,才会有 object instanceof jvm.part7.LoadingTest
打印 true
的结果;并且两种方式获取的类加载器相同。
自定义类加载器:
java
/**
* 自定义类加载器
*/
public class SelfDefineClassLoader extends ClassLoader{
private String classLoaderName; //标示性的属性,类加载器的名字
private final String fileExtension = ".class"; //每一次中磁盘上读取的文件的扩展名
public SelfDefineClassLoader(String classLoaderName){
super(); //将系统类加载器当做该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
// 该方法的前提是已经有了parent这样一个类加载器
public SelfDefineClassLoader(ClassLoader parent, String classLoaderName){
super(parent); //显示执行该类加载器的父加载器(parent)
this.classLoaderName = classLoaderName;
}
@Override
public String toString() {
return "[" + this.classLoaderName + "]";
}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
byte[] data = this.loadClassData(className);
return this.defineClass(className,data,0,data.length); //最终返回一个字节class对象
}
/**
* 通过类的名字(className),把对应的文件的名字找到,并以输入输出流的形式最后返回一个字节数组,这个
* 字节数组就是从class文件中读取的二进制信息。
*/
private byte[] loadClassData(String className){
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
try {
this.classLoaderName = this.classLoaderName.replace(".","/");
is = new FileInputStream(new File(className + this.fileExtension));
int ch;
while (-1 != (ch=is.read())){
baos.write(ch);
}
data = baos.toByteArray();
}catch (Exception ex){
ex.printStackTrace();
}finally {
try {
baos.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception {
SelfDefineClassLoader classLoader = new SelfDefineClassLoader("selfDefineClassLoader");
// 1.下面这个调用的loadClass方法,其实调用的还是ClassLoader类中的方法,
// 2.loadClass方法会去调用我们上面所重写的 findClass方法,根据二进制名字寻找
// class对象(这里是MyTest10类)。 【这也是自定义类最关键的一环,重写findClass方法】
Class<?> clazz = classLoader.loadClass("jvm.part7.LoadingTest");
Object object = clazz.newInstance();
System.out.println(object);
System.out.println("使用的类加载器:" + object.getClass().getClassLoader());
System.out.println(object instanceof jvm.part7.LoadingTest);
System.out.println("使用的类加载器:" + jvm.part7.LoadingTest.class.getClassLoader());
}
}
/**
* 被加载的类
*/
public class LoadingTest {
}
打印结果:
java
jvm.part7.LoadingTest@43556938
使用的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
true
使用的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
4.3 Class.forName()
在介绍一下 java.lang.Class#forName(java.lang.String)
与 java.lang.ClassLoader#loadClass(java.lang.String, boolean)
的区别。
通过下面两种方式加载ClassForNameTest的方式的案例可以得出:
因为我们知道一个类如果被初始化的,会先对类中的静态代码块和静态变量赋值;或者我们也可以知道类的主动使用的7种方式就有
Class.forName()
【可以看 JVM-类加载机制 ,初始化时机这节内容】,主动使用会引起类的初始化。 所以,Class.forName加载类时将类进行初始化,而ClassLoader的loadClass并没有对类进行初始化,只是把类加载到了虚拟机中。
-
java.lang.Class#forName(java.lang.String)
javapublic class ClassForNameTest { // 静态代码块 static { System.out.println("静态代码块执行了!"); } // 静态变量 private static String staticStr = stringMethod(); // 给静态变量复制的方法 public static String stringMethod(){ System.out.println("静态方法执行了!"); return "静态变量"; } public static void main(String[] args) { try { Class.forName("jvm.part7.ClassForNameTest"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } // 执行结果: 静态代码块执行了! 静态方法执行了!
-
java.lang.ClassLoader#loadClass(java.lang.String, boolean)
javapublic class LoadingTest { // 这里还是之前 ClassLoaderTest中重写loadClass(String name)方法,拿过来直接用 private static ClassLoader getSelfClassLoader() { ClassLoader myLoader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(fileName); if (is == null) { return super.loadClass(name); } byte[] b = new byte[is.available()]; is.read(b); return defineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(name); } } }; return myLoader; } public static void main(String[] args) { try { // 用加载器加载 jvm.part7.ClassForNameTest类 getSelfClassLoader().loadClass("jvm.part7.ClassForNameTest"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } // 执行结果: 没有任何的打印输出
五、破坏双亲委派模型
破坏双亲委派模型------线程上下文类加载器。
第二章节初始化流程,源码的第三步:
Thread.currentThread().setContextClassLoader(loader);
线程上下文类加载器(Context Classloader)
-
线程上下文类加载器从JDK 1.2开始引入的,类Thread中的
getContextClassLoader()
与setContextClassLoader(ClassLoader c1)
分别来获取和设置上下文类加载器。 -
如果没有通过
setContextClassLoader(ClassLoader c1)
进行设置的话,线程将继承父类线程的上下文类加载器。Java应用运行时的初始线程的上下文加载器是系统类加载器。在线程中运行的代码可以通过该类加载器来加载类与资源。 -
线程上下文加载器的重要性:
- SPI (Service Provider Interface)
- 父ClassLoader可以使用【当前线程
Thread.currentThread().getContextClassLoader()
所执行的classLoader加载】的类。**这就改变了父ClassLoader不能使用子ClassLoader或是其他没有直接父子关系的ClassLoader加载的类的情况,**即改变了双亲委托模型。
-
线程上下文类加载器就是当前线程的Current ClassLoader
- 在双亲委托模型下,类加载是由下至上的,即下层的类加载器会委托上层进行加载。但是对于SPI来说,有些接口是Java核心库所提供的的,而Java核心库是由根类加载器来加载的,而这些接口的实现却来自于不同的jar包(厂商提供),Java的根类加载器是不会加载器其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求。而通过给当前线程设置上下文类加载器,就可以由设置的上下文类加载器来实现对于接口实现类的加载。
下面我们通过Java虚拟机如何加载不同厂商的
JDBC
驱动,来介绍Java体系如何通过 线程上下文类加载器来破坏双亲委派机制,实现对对三方SPI jar包的加载的。。 哎呀,这里要对JDBC驱动做介绍,应该在单独搞一篇文章介绍,想理解一个知识点,牵扯的内容太多了 ^ _ ^ '
5.1 JDBC驱动
连接数据库并操作数据库的大致步骤:
我们主要讲的就是第一步的:在使用JDBC前,应该保证相应的Driver类已经被加载到Java虚拟机中,并初始化(Class.forName()本身就是使类初始化,前面我们介绍过),看看是如何实现的。
java
public class ThreadContextClassLoader {
public static void main(String[] args) throws Exception {
// 1. 注册JDBC驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 连接数据库
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","123456");
// 3. 执行查询
// 4. 返回结果集
// 5. 关闭数据库连接
}
}
首先我们知道,不同的数据库,MsSQL,Oracle都有适配Java的jar包,来实现Java对不同数据的应用,这个时候,Java提供数据库驱动接口,让不同的厂商实现,具体细节有厂商提供;但是各自厂商的jar包如果加载到虚拟机内存中,并能够被Java虚拟机所加载,这个时候 线程上下文类加载器就排上了用场,也就是上面提到的线程上下文类加载器的作用。
位于JDK源码 rt.jar包下的 java.sql.Driver
就是提供给不同厂商的JDBC驱动接口。
java
/**
* 对接口注释翻译了一下:
* (这里所说的驱动程序,就是不同的数据库驱动,jdbc.jar)
* 每个驱动程序类必须实现的接口,JavaSQL框架允许多个数据库驱动程序。
* 每个驱动程序都应该提供一个实现驱动程序接口的类。
*
* DriverManager将尝试加载尽可能多的驱动程序,然后对于任何给定的连接请求,它将依次要求每个驱动程序尝试连接到目标URL。
* 强烈建议每个驱动程序类都应该是小的、独立的,这样可以在不引入大量支持代码的情况下加载和查询驱动程序类。
*
* 当一个Driver类被加载时,它应该创建一个自己的实例并将其注册到DriverManager中。
* 这意味着用户可以通过调用:Class.forName("foo.bah.Driver")
* JDBC驱动程序可以创建一个DriverAction实现,以便在调用DriverManager.deregisterDriver时接收通知。
*/
public interface Driver {
}
-
我这里用的数据库是MySQL,是MySQL驱动,我们进入MySQL的JDBC jar包
-
位于
mysql-connection-java.jar
包中:com.mysql.jdbc.Driver
可以看到只有空构造方法和一个静态代码块,我们知道,当我们调用
Class.forName("com.mysql.jdbc.Driver");
的时候,会先执行静态代码块的内容。javapublic class Driver extends NonRegisteringDriver implements java.sql.Driver { // Register ourselves with the DriverManager static { try { // 将 该数据库驱动,这里是MySQL数据库驱动, Driver()注册到 DriverManager驱动管理器中 java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } /** * Construct a new driver and register it with DriverManager * @throws SQLException if a database error occurs. */ public Driver() throws SQLException { // Required for Class.forName().newInstance() } }
-
我们进入静态代码块的唯一一行代码中:
java.sql.DriverManager.registerDriver(new Driver());
**这里我们需要注意,该类是在 JDK的 rt.jar包中的。**是数据库驱动管理器,不同的数据库驱动将注册到
DriverManager.registeredDrivers
属性上。 两个
registerDriver
方法实现将传递过来的驱动注册到DriverManager.registeredDrivers
上javapublic class DriverManager { // 不同的数据库驱动将注册到 DriverManager.registeredDrivers属性上。 private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>(); static { // 加载并初始化数据库JDBC驱动!!!!! loadInitialDrivers(); println("JDBC DriverManager initialized"); } /** * 向DriverManager注册给定的驱动程序。 * 新加载的驱动程序类应该调用registerDriver方法,以使DriverManager知道它自己。 * 如果驱动程序当前已注册,则不采取任何操作 */ public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException { registerDriver(driver, null); } public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException { /* Register the driver if it has not already been added to our list */ if(driver != null) { registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); } else { // This is for compatibility with the original DriverManager throw new NullPointerException(); } println("registerDriver: " + driver); } }
-
但是,我们需要注意,这里直接对驱动进行注册了,使用这个代码的前提是我们已经将类加载并初始化了,但是 我们注册的
com.mysql.jdbc.Driver
是数据库驱动里面mysql-connection-java.jar
包里面的类,Java类加载器并不能直接加载,我们怎么办呢。 可以看到,DriverManager类里面有一个静态代码块,也就说,我们在使用该类DriverManager的时候,静态代码块先执行。
也就是
loadInitialDrivers();
方法!!!javapublic class DriverManager{ // 加载并初始化数据库JDBC驱动 !!!!! private static void loadInitialDrivers() { // 1. 这里是查看系统属性有没有配置jdbc驱动,可以配置多个,用","逗号隔开 // 是手动配置的 // ege: System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver,"); String drivers; try { drivers = AccessController.doPrivileged(new PrivilegedAction<String>() { public String run() { return System.getProperty("jdbc.drivers"); } }); } catch (Exception ex) { drivers = null; } // 如果驱动程序包为服务提供商,请加载它。 // 通过作为java.sql.Driver.class服务公开的类加载器获取所有驱动程序。 // 有ServiceLoader.load(Driver.class)完成 AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { // 2. 核心代码 // 利用ServiceLoader.load(Driver.class)的作用,就是为所有实现了Driver.class的JDBC驱动 // 利用上下文类加载器加载对应数据库JDBC的驱动。 // 是为什么用上下文类加载器加载呢,因为ServiceLoader是JDK rt.jar下的类,由根类加载器器加 // 载。但是,根类加载器不能加载外部jar包,所以,这里就是破坏双亲委派模型的位置,由线程上下文类加 // 载器加载数据驱动 即Driver的子类实现(各个数据库驱动) ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); // ServiceLoader封装了 数据库驱动和线程上线文类加载器,这里遍历加载这些驱动。 try{ while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing } return null; } }); // 3. 下面这代码的作用是加载(1.)获取到的系统配置文件参数中配置的 驱动类 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); } } } }
OK,到这里我们找到了 破坏双亲委派机制的位置!!!
5.2 ServiceLoader
下面我们主要看 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
ServiceLoader类的几个作用:
-
1、这个类就是一个服务提供商加载工具;并不仅仅局限于去加载 数据库驱动;
-
2、为了加载,服务由单个类型表示,即单个接口或抽象类。就是下面的泛型
<S>
要有具体的类型; -
3、通过在资源目录
META-INF/services
中放置提供者配置文件来标识服务提供者;下面的PREFIX
属性就这找这么文件标识,下面的图一就是MySQL JDBC
源码包加载标识;这个类
LazyIterator
是ServiceLoader
的一个内部类,可以看到在这个位置对驱动的标识名进行获取。(先了解,下面会将这一步如何执行到的) -
4、懒加载 lookupIterator属性的作用
服务的定位和实例化都是惰性的,即按需进行。服务加载器维护迄今已加载的提供程序的缓存。
迭代器方法的每次调用都返回一个迭代器,该迭代器调用遍历的时候,就是利用 类加载器和服务的绑定关系,使用类加载对服务进行加载,初始化操作。
可以Debug看一下,下图就是数据库驱动接口和应用类加载器(获取的当前线程类加载器)两个参数。
javapublic final class ServiceLoader<S> implements Iterable<S> { private static final String PREFIX = "META-INF/services/"; private final Class<S> service; // 表示正在加载的服务的类或接口 (就是驱动接口) private final ClassLoader loader; // 其实就是加载 service类的 类加载器 private final AccessControlContext acc; // 创建ServiceLoader时获取的访问控制上下文 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 会对驱动程序进行缓存,这里做缓存 // 会将驱动程序和加载该驱动的类加载器封装成LazyIterator // 驱动程序加载的方式为懒加载,即:首次主动调用的时候才加载,下面会对这个类进行讲解,它是ServiceLoader的一个内部类 private LazyIterator lookupIterator; /** * 使用当前线程的上下文类加载器为给定的服务类型创建新的服务加载器。 * 我们这里对的 数据库驱动做加载,所以其实就是当前上下文类加载器和数据库驱动做绑定; * 后面用 当前上下文类加载器 加载 对应的数据库驱动 */ 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); } // 上面方法调用这个方法,类加载接着传递 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); } }
-
在上面的load过程中,我们再看看
lookupIterator = new LazyIterator(service, loader);
LazyIterator
是ServiceLoader
的一个内部类,就是为了类加载器懒加载服务(驱动)javapublic final class ServiceLoader<S> implements Iterable<S> { private class LazyIterator implements Iterator<S> { /** * 这里将 驱动和类加载器绑定,我们看到这类实现了 Iterator接口;也就是说LazyIterator就是一个迭代器 * 后面会利用迭代的遍历方式使用类加载loader对驱动service进行加载 */ private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; } } }
走到这里,我们把
ServiceLoader.load(Driver.class)
介绍完了,整个过程大致思路很简单就是将 service(驱动),loader(类加载)放进 LazyIterator,现在没有实现加载。
5.3 LazyIterator
徒增章节。
ServiceLoader.load(Driver.class)
执行完之后,我们回到主流程中;下面就开始遍历加载驱动了。
java
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
// ServiceLoader封装了 数据库驱动和线程上线文类加载器,这里遍历加载这些驱动。
try{
while(driversIterator.hasNext()) {
driversIterator.next(); // 下面就开始遍历加载驱动了。
}
}
-
LazyIterator
-
作用:将 驱动和类加载器绑定,我们看到这类实现了 Iterator接口;也就是说LazyIterator就是一个迭代器;后面会利用迭代的遍历方式使用类加载loader对驱动service进行加载
-
遍历:
-
方法一重写迭代器
hasNext()
方法,判断是否下一个节点;方法一调用方法二hasNextService()
获取驱动的全称限定名,找到驱动文件。 -
方法三重写迭代器
next()
方法,获取当前节点信息;方法三调用方法四nextService()
,利用绑定类加载器加载驱动类。
-
javapublic final class ServiceLoader<S> implements Iterable<S> { private class LazyIterator implements Iterator<S> { /** * 这里将 驱动和类加载器绑定,我们看到这类实现了 Iterator接口;也就是说LazyIterator就是一个迭代器 * 后面会利用迭代的遍历方式使用类加载loader对驱动service进行加载 */ private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; } /** * 方法一:hasNext() * 迭代器,查询下一个节点是否有 驱动程序(服务程序) * 直接重写 hasNext() ,调用 hasNextService(); */ public boolean hasNext() { if (acc == null) { // 调用hasNextService() return hasNextService(); } else { PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { // 调用hasNextService() public Boolean run() { return hasNextService(); } }; return AccessController.doPrivileged(action, acc); } } /** * 方法二:hasNextService() * 加载驱动的文件,被返回configs */ private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { // 这里,获取驱动的全称限定名 ,我们这里数据库文件的全称限定名是: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; } /** * 方法三:next() * 重写next,获取当前节点 */ public S next() { if (acc == null) { // 调用nextService(); return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { // 调用nextService(); 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 { // 利用loader类加载器对cn驱动程序进行加载 // 第二个参数为 false,说明只对类做加载,而不做初始化操作 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 } } }
-
5.4 总结
回过头来,总结一下!
像数据库驱动这样的由第三方厂商提供的jar包,Java虚拟机的类加载机制的双亲委派模型是加载不了的,因此就有了破坏双亲委派模型------线程上下文类加载器。
JDK提供了
ServiceLoader
类实现对 第三方服务程序的加载,DriverManager
实现对驱动程序的管理。 对于数据库驱动,JDK提供了
java.sql.Driver
驱动接口,让 第三方服务程序实现,以便在第三方驱动jar包加载的时候,能够利用ServiceLoader
找到 线程上下文类加载器和驱动类的绑定关系,进行加载驱动。
写了两星期,终于写完了。