1.概述
classloader 的作用是加载字节码到jvm,有些情况下,我们比如使用插件模式,可能需要自定义从外部加载插件到jvm。这里还有一个需要注意的是java的双亲委派机制。
2.实现过程
2.1.定义自定义classloader
package com.example.demo.loader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] loadClassData(String className) {
// 将包名转换为路径
String fileName = classPath + File.separator +
className.replace('.', File.separatorChar) + ".class";
try (InputStream is = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
int ch;
while ((ch = is.read()) != -1) {
baos.write(ch);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
这个代码继承 classloader,并重写了findClass.
2.2.定义外部类
package com.example.demo.loader;
public class MyDynamicClass {
public void sayHello() {
System.out.println("Hello from dynamically loaded class!");
}
}
2.3. 使用自定义classloader
package com.example.demo.loader;
public class ClassLoaderDemo {
public static void main(String[] args) {
try {
// 指向 .class 文件的根目录
MyClassLoader loader = new MyClassLoader("D:\\work\\research\\demo\\target\\classes");
// 加载类(注意使用全限定名)
Class<?> clazz = loader.loadClass("com.example.demo.loader.MyDynamicClass");
// 创建实例
Object instance = clazz.getDeclaredConstructor().newInstance();
// 调用方法
clazz.getMethod("sayHello").invoke(instance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
这样我们可以看到 MyDynamicClass 的输出。
Hello from dynamically loaded class
这个时候,如果我将这个 MyDynamicClass 拷贝到D:\temp\class 目录下。
我们更改代码:
MyClassLoader loader = new MyClassLoader("D:\temp\class");
这个时候,我在修改一下MyDynamicClass 的输出
package com.example.demo.loader;
public class MyDynamicClass {
public void sayHello() {
System.out.println("测试一下输出!");
}
}
这个时候在ClassLoaderDemo执行一下,我们发现输出是"测试一下输出!",并没有使用D:\temp\class 下的class
这里涉及到一个 java的双亲委派机制。
2.3 什么是双亲委派机制
双亲委派机制是Java类加载器的一种工作模式,当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把请求委托给父类加载器去完成,依次向上递归,只有当父类加载器无法完成加载时,子加载器才会尝试自己加载。
2.4 如果不想使用双亲机制
我们的类可以这么修改一下
Class<?> clazz = loader.findClass("com.example.demo.loader.MyDynamicClass");
这个就是没有走 loadClass 方法,直接调用 findClass方法,获取到外部类。
2.5 双亲委派机制的实现原理代码
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 首先,检查请求的类是否已经被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 如果父加载器不为空,则委托给父加载器加载
c = parent.loadClass(name, false);
} else {
// 如果父加载器为空,则委托给启动类加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父加载器无法加载,抛出ClassNotFoundException
}
if (c == null) {
// 如果父加载器无法加载,则调用自己的findClass方法进行加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
从这个代码我们就可以看出,因为 在classpath下已经找到了 实现类,所以就不会在使用自定义classload加载的类。
如果直接从class path 下删除哪个 MyDynamicClass class 文件,我们可以看到自定义 classloader是生效的。
浙公网安备 33010602011771号