介绍
Java是一个纯面向对象的语言,Java的体系结构是由一个一个的类构成的。类加载是将.class文件定义为JVM中一个类的过程,也是使用一个类的前提条件。每一个类由:它的全限定名+它的类加载器
唯一确定。
类加载器是一个抽象类:abstract ClassLoader。JDK给我们实现了三个类加载器,BootStrapClassLoader、ExtClassLoader、AppClassLoader。
双亲委派机制
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就加载后返回,否则交给子类加载器完成。
特点
- 安全,可避免用户自己编写的类动态替换Java的核心类,如java.lang.String
- 避免全限定命名的类重复加载,使用了findLoadClass()判断当前类是否已加载
代码分析
loadClass
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
// 如果没有被记载
if (c == null) {
long t0 = System.nanoTime();
try {
// 有爹就找他爹去加载
if (parent != null) {
c = parent.loadClass(name, false);
// 没爹就找BootStrap加载器去加载
} 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.
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;
}
}
findClass
这个方法是上面第29行被调用的方法。如果想保持双亲委派机制,在自定义类加载器的时候不去覆盖loadClass,去覆盖findClass就可以了。
自定义类加载器的例子
package org.zzj;
public class User {
}
package org.zzj;
public class UserService {
public void add() {
try {
System.out.println(Class.forName("org.zzj.User").getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
package org.zzj;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
public class ClassForNameTest {
public static void main(String[] args) throws Exception {
// 当前调用者的加载器是 sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(Class.forName("org.zzj.User").getClassLoader());
MyClassLoader classLoader = new MyClassLoader();
// clazz是 org.zzj.MyClassLoader@5b2133b1 加载的
Class<?> clazz = classLoader.loadClass("org.zzj.UserService");
System.out.println("aaa -> " + clazz.getClassLoader());
Method method = clazz.getMethod("add");
method.invoke(clazz.newInstance());
}
}
class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println(name);
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream in = getClass().getResourceAsStream(fileName);
if (in == null) {
return super.loadClass(name);
}
byte[] b = null;
try {
b = new byte[in.available()];
in.read(b);
in.close();
} catch (IOException e) {
e.printStackTrace();
}
// 将 字节数组 转为 类
return defineClass(name, b, 0, b.length);
}
}
Class.forName和ClassLoader.loadClass的区别
Class.forName加载的类会进行初始化,而ClassLoader.loadClass加载的类不会进行初始化:
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
需要注意的是这个使用这个方法加载得到的类默认是调用者的类加载器。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
线程上下文类加载器
Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。
这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器(Bootstrap Classloader)来加载的;SPI的实现类是由系统类加载器(System ClassLoader)来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类。
而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。