Class.forName 和 ClassLoader.loadClass 都可以用来反射加载一个类,主要有2种区别:
使用的类加载器不一样
Class.forName 最终是使用调用方的 ClassLoader 来加载类,在代码中使用 Reflection.getCallerClass() 方法获取类加载器来加载类
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
Class<?> caller = null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
caller = Reflection.getCallerClass();
if (sun.misc.VM.isSystemDomainLoader(loader)) {
// 使用调用方的 ClassLoad 加载类
ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader, caller);
}
ClassLoader.loadClass 遵循类加载的双亲委派模型类加载类,在代码中使用 ClassLoader parent 加载类
// The parent class loader for delegation
if (parent != null) {
c = parent.loadClass(name, false);
}
双亲委托模型:当一个类加载器接收到一个类加载的任务时,不会立即展开加载,而是将加载任务委托给它的父类加载器去执行,每一层的类都采用相同的方式,直至委托给最顶层的启动类加载器为止。如果父类加载器无法加载委托给它的类,便将类的加载任务退回给下一级类加载器去执行加载。
类的装载过程不一样
类的装载过程为:
链接
|
--------------------------
加载 ----> | 验证 ----> 准备 ----> 解析| ----> 初始化 ----> 使用 ----> 卸载
--------------------------
Class.forName 会装载类到初始化阶段,因此会执行类中的静态方法块,调用 forName0 时传入的 initialize 为 true
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
// initialize = true
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
ClassLoader.loadClass 会装载类到加载节点,不会执行链接操作,因此不会执行类中的静态方法块,在代码中调用 loadClass 时传入的 resolve 为 false,表示不链接类
if (parent != null) {
// resolve = false
c = parent.loadClass(name, false);
}
如:
class Test {
static {
System.out.print("loaded");
}
}
使用 Class.forName 加载 Test 类会输出 "loaded",而使用 ClassLoader.loadClass 加载则不会输出 "loaded"