JAVA通过类加载器名称、包名、类名、方法名唯一确定一个方法,因此JAR包冲突可以通过改变类加载器实现。JAVA8及以下版本破坏双亲委派机制,实现JAVA类隔离加载依赖 loadClass()方法实现。JAVA9开始模块化后,加载器之间的关系不再是双亲委派模型的树状结构,自然可以隔离不同类的加载。
建立maven工程,在 com.example 包下有TestA和TestB两个类。TestA调用了TestB中的方法。现希望将TestA与其所有的引用,也就是TestB类使用同一个自定义类加载器加载。代码如下。TestA与TestB是测试类,MyClassLoaderCustom是自定义类加载器。如果不使用这个类加载器,在使用Main类的main方法启动项目调用TestA的main时,TestA与TestB会被AppClassLoader加载;经过重写loadClass方法的处理后,TestA和TestB都使用自定义类加载器进行加载。
package com.example;
public class TestA {
public static void main(String[] args) {
TestA testA = new TestA();
testA.hello();
}
public void hello() {
System.out.println("com.example.TestA: " + this.getClass().getClassLoader());
TestB testB = new TestB();
testB.hello();
}
}
package com.example;
public class TestB {
public void hello() {
System.out.println("com.example.TestB: " + this.getClass().getClassLoader());
}
}
package com.example;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class MyClassLoaderCustom extends ClassLoader {
private ClassLoader jdkClassLoader;
private Map<String, String> classPathMap = new HashMap<>();
public MyClassLoaderCustom(ClassLoader jdkClassLoader) {
this.jdkClassLoader = jdkClassLoader;
classPathMap.put("com.example.TestA", TestA.class.getResource("").getPath()+"TestA.class");
classPathMap.put("com.example.TestB", TestB.class.getResource("").getPath()+"TestB.class");
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class result = findLoadedClass(name);
// 这里是为了防止类重复加载
if(result==null){
try {
//这里要使用 JDK 的类加载器加载 java.lang 包里面的类
result = jdkClassLoader.loadClass(name);
} catch (Exception ignored) {
}
if (result != null) {
return result;
}
String classPath = classPathMap.get(name);
File file = new File(classPath);
if (!file.exists()) {
throw new ClassNotFoundException();
}
byte[] classBytes = getClassData(file);
if (classBytes.length == 0) {
throw new ClassNotFoundException();
}
return defineClass(name, classBytes, 0, classBytes.length);
}
return result;
}
private byte[] getClassData(File file) {
try (InputStream ins = new FileInputStream(file); ByteArrayOutputStream baos = new
ByteArrayOutputStream()) {
byte[] buffer = new byte[4096];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[]{};
}
}
package com.example;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception {
//这里取AppClassLoader的父加载器也就是ExtClassLoader作为MyClassLoaderCustom的jdkClassLoader
MyClassLoaderCustom myClassLoaderCustom = new MyClassLoaderCustom(Thread.currentThread().getContextClassLoader().getParent());
Class testAClass = myClassLoaderCustom.loadClass("com.example.TestA");
Method mainMethod = testAClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[]{args});
}
}
输出结果可以看到,两个类都使用了自定义类加载器进行加载。
com.example.TestA: com.example.MyClassLoaderCustom@135fbaa4
com.example.TestB: com.example.MyClassLoaderCustom@135fbaa4
参考网站
如何实现Java类隔离加载?
呵,十个双亲委派问题,想问倒我?
如何手动获取maven项目中resource目录下的文件(包括二级目录下的文件)
ClassLoader源码分析