自定义类加载器
导航
- 一、loadClass(String) 方法
- 二、findClass(String) 方法
- 三、自定义类加载器
- 四、打破双亲委派机制
一、loadClass(String) 方法
想要把一个类加载到内存中,只需要调用 ClassLoader 的 loadClass 方法:
public class TestLoadClass {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> cls = TestLoadClass.class.getClassLoader()
.loadClass("test86.LoadClass");
System.out.println(cls.getName());
}
}
output:
test86.LoadClass
cls.getName 可以成功取得类名,而不是抛出 NPE,说明类已经被成功加载。
loadClass 方法如果成功执行,会将磁盘上的字节码文件加载到内存,并生成一个对应的 Class 实例,然后将这个 Class 对象返回。
loadClass 方法的源码如下:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
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);
} 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);
}
}
return c;
}
}
loadClass 的处理过程实际上就是双亲委派机制的实现:
而 loadClass 最终会调用 findClass 来完成类加载的工作。
二、findClass(String) 方法
在 ClassLoader 中它的定义如下:
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
显而易见,该方法应该由 ClassLoader 的子类来实现,否则就会抛出 ClassNotFound 异常。(参考模板方法模式)
AppClassLoader 和 ExtClassLoader 都继承了 URLClassLoader,它们的 findClass 也是在URLClassLoader 中定义的:
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);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
throw new ClassNotFoundException(name);
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
}
当然,这段代码不需要细扣,只需要了解这个方法会根据传入的类引用全名转化后的磁盘路径最终由 defineClass 方法返回。
三、自定义类加载器
了解了 loadClass 和 findClass 方法后,如何自定义一个类加载器呢?
loadClass方法实现了双亲委派模型的处理逻辑,在不需要打破这种底层机制的情况下是不需要重写的,而 findClass 使用的模板方法模式要求 ClassLoader 的子类都必须重写 findClass 方法。
因此,自定义类加载器必须要重写 findClass,一般情况也只需要重写 findClass 。
首先,我们将需要加载的类先编译好,放入指定目录下:
然后尝试继承 ClassLoader 并重写 findClass:
public class MortyClassLoader extends ClassLoader {
public static void main(String[] args) throws ClassNotFoundException {
MortyClassLoader mortyClassLoader = new MortyClassLoader();
Class<?> cls = mortyClassLoader.loadClass("test86.LoadClass");
System.out.println(cls.getName());
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File clsFile = new File("D:/workspace/morty/",
name.replace(".", "/").concat(".class"));
try {
FileInputStream fis = new FileInputStream(clsFile);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b = fis.read()) != 0) {
baos.write(b);
}
byte[] bytes = baos.toByteArray();
// 此处的 close 并不严谨
baos.close();
fis.close();
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
// throws ClassNotFoundException
return super.findClass(name);
}
}
output:
test86.LoadClass
执行后可以正常取得类名信息。
四、打破双亲委派机制
双亲委派机制是 JVM 保证系统安全性的一个重要手段,但这种机制并不牢靠。换句话说,如果情景需要,依然是可以绕开双亲委派机制。
历史上有三次大规模的“破坏”双亲委派机制。
- JDK 1.2 之前,还没有“双亲委派模型”,用户可以继承 ClassLoader 并重写 loadClass 方法。
- JDK 1.2 之后,已不提倡用户再去覆盖 loadClass 方法。但是双亲委派模型有其自身的缺陷,双亲委派模型很好的解决了基础类,如Object 等的统一问题,一般而言,基础类往往总是被用户代码调用,但是JNDI 服务中,API 定义服务的统一接口,JNDI 的目的就是对资源进行集中管理和查找。它需要调用实现了特定 API 接口的独立厂商实现并部署在用户应用程序classpath下的 JNDI 接口提供者(SPI,Service Provider Interface),例如各种符合 JDBC 接口规范的数据库厂商的驱动包。而启动类加载器是无法加载这些类库的,为解决这个问题,引入了线程上下文类加载器(Thread Context ClassLoader)。而JNDI 服务就可以使用这个 线程上下文类加载器去加载所需要的SPI代码,就是 父类加载器请求子类加载器去完成类加载动作,已经违背了双亲委派模型的一般性原则。如 JDBC、JNDI 等。
- 热加载、热部署等也是破坏了双亲委派模型。
还没有评论,来说两句吧...