一、背景
之前蒋老师看到《当月亮守护地球 | SkyWalking Agent 守护你的应用...有它相伴才安逸》时,有对其中这行文字小有斟酌。
类加载具有传递性:如当需要加载一个类时,JVM 默认会使用(当前需要使用此类的)调用者 Class 对象的 ClassLoader 来加载此类;举例:类 A 中 import 类 B,那么类 A 的 ClassLoader 会去加载类 B。
上边这段文字看起来有点微妙,传递性好像第一次听说,这也导致了蒋老师随之表示,接下来要全面的学习ClassLoader
(嗯,公众号又有高质量文章可以转载了)。
本篇是对ClassLoader
中 类的隐式加载和显式加载做个直观的介绍和验证。这个知识点笔者个人认为很重要,多次帮助笔者解决日常工作中遇到的疑难杂症,如果你尚未认真研究过ClassLoader
,但懵懂的认知让你觉得这个应该很简单,那请看下图,若看不懂则表明你可能并不了解ClassLoader
中得一些关键逻辑;不贩卖焦虑,不感兴趣则忽略,知道个大概即可,不会它并不影响你做一个优秀的程序员。
类加载关键逻辑图示(来自网络).png
二、问题与答案
1) 问题
通常我们都知道我们写的类,会被 JVM 加载,那都怎么被加载呢?
2)答案
- 类最常见的方式是通过
import
的方式引入的,那么 JVM 自动帮我们去加载。 - 另外一种情况是我们指定类加载器去加载,如使用
Class#forName(xxx)
或者ClassLoader#loadClass(xxx)
。
三、什么是自动加载
- 验证我们的代码中被
import
导入的类,在使用的时候会被当前类加载器自动加载,就是使用当前类加载器的loadClass
方法。 - 当前类加载器怎么理解, 当前类被哪个类加载器加载的,那么这个类加载器就叫做这个类的当前类加载器。那么这个类中通过
import
方式引入了其他类,就被 JVM 自动的使用当前类加载器的loadClass
方法加载。
这个很好验证,ClassLoader 的 loadClass 方法里自己打个断点,调试一下。
四、什么是非自动加载
非自动加载就是我们指定一个类加器去加载类,根据需要从forName
、loadClass
中二选一。
问自己一个问题,forName
是不是要遵守双亲委派模型?双亲委派的代码逻辑在 ClassLoader#loadClass 中,那是不是 forName 还会调用 ClassLoader 的 loadClass 的方法逻辑呢?答案是:会的,通过一个例子验证一下 。
1)示例准备
- 接口定义
public interface MsgCenter { public boolean sendMsg(String msg); } 复制代码
- 接口实现:
``` public class MsgValidater { public boolean validate(String msg){ return true; } } public class MsgCenterImpl implements MsgCenter { @Override public boolean sendMsg(String msg) { // import MsgValidater类型 return new MsgValidater().validate(msg); } } ``` 复制代码
2)编排测试方案
测试的关键逻辑如下:
- 先通过
appClassLoader
加载接口 - 再通过子加载器以
forName
方式加载实现类 - 实现类实例化后并赋值给接口;调用
sendMsg
方法,此方法内引用了MsgValidater
类。
逻辑看似复杂,只要稍微耐心一点也没什么,无非是主动加载和自动加载的组合再加上双亲委派的机制进行了一场混沌验证;笔者个人的经验是,学习这类知识最好的办法是反复调试并做好小笔记,用思维导图梳理自己的认知模型,感兴趣的话还是要调试一下,不感兴趣就别再看了。
/** * 把MsgCenterImpl.class 移动到子加载器的加载路径中.保证只有子加载器可加载. */ @Test public void testImportLoadClass(){ CustomClassLoader01 customClassLoader01 = new CustomClassLoader01(ClassLoader.getSystemClassLoader()); try { //appClassloader 加载 接口 MsgCenter msgCenter = null; //子加载器,加载 实现类 Class<?> aClass = Class.forName("com.rock.MsgCenterImpl",false,customClassLoader01); //赋值 接口 = 实现类 ;这是可以的,因为子加载器可见父加载器所加载的类. msgCenter =(MsgCenter)aClass.newInstance(); boolean hello = msgCenter.sendMsg("hello"); System.out.println("after sendMsg"); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } 复制代码
3)结果符合预期
CustomClassLoader start load class :com.rock.MsgCenterImpl ; resolve : false CustomClassLoader findLoadedClass : null CustomClassLoader getParent().loadClass(name) : ClassNotFoundException CustomClassLoader loadClassData : com.rock.MsgCenterImpl //以上信息说明:forName方法通过自定义类加载器的loadClass方法加载了com.rock.MsgCenterImpl CustomClassLoader start load class :com.rock.MsgCenter ; resolve : false CustomClassLoader findLoadedClass : null CustomClassLoader start load class :java.lang.Object ; resolve : false CustomClassLoader findLoadedClass : null //以上信息说明:子加载器 委托父加载器加载 com.rock.MsgCenter ,java.lang.Object CustomClassLoader start load class :com.rock.classLoader.MsgValidater ; resolve : false CustomClassLoader findLoadedClass : null CustomClassLoader getParent().loadClass(name) : ClassNotFoundException CustomClassLoader loadClassData : com.rock.classLoader.MsgValidater //以上信息说明:自定义加载器加载了com.rock.classLoader.MsgValidater after sendMsg //以上信息说明,实现类赋值给接口后,调用接口的方法执行成功. 复制代码