java - “java.lang.LinkageError:以前为具有名称的不同类型启动加载”的原因
问题描述
我读过一篇文章https://frankkieviet.blogspot.com/2009/03/javalanglinkageerror-loader-constraint.html并使用演示代码来模拟 LinkageError。
/**
* A self-first delegating classloader. It only loads specified classes self-first; other
* classes are loaded from the parent.
*/
private static class CustomCL extends ClassLoader {
private Set<String> selfFirstClasses;
private String label;
public CustomCL(String name, ClassLoader parent, String... selfFirsNames) {
super(parent);
this.label = name;
this.selfFirstClasses = new HashSet<String>(Arrays.asList(selfFirsNames));
}
public Class<?> findClass(String name) throws ClassNotFoundException {
if (selfFirstClasses.contains(name)) {
try {
byte[] buf = new byte[100000];
String loc = name.replace('.', '/') + ".class";
InputStream inp = Demo.class.getClassLoader().getResourceAsStream(loc);
int n = inp.read(buf);
inp.close();
System.out.println(label + ": Loading " + name + " in custom classloader");
return defineClass(name, buf, 0, n);
} catch (Exception e) {
throw new ClassNotFoundException(name, e);
}
}
// Is never executed in this test
throw new ClassNotFoundException(name);
}
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (findLoadedClass(name) != null) {
System.out.println(label + ": already loaded(" + name + ")");
return findLoadedClass(name);
}
// Override parent-first behavior into self-first only for specified classes
if (selfFirstClasses.contains(name)) {
return findClass(name);
} else {
System.out.println(label + ": super.loadclass(" + name + ")");
return super.loadClass(name, resolve);
}
}
public String toString() {
return label;
}
}
public static class User {
}
public static class LoginEJB {
static {
System.out.println("LoginEJB loaded");
}
public static void login(User u) {
}
}
public static class Servlet {
public static void doGet() {
User u = new User();
System.out.println("Logging in with User loaded in " + u.getClass().getClassLoader());
LoginEJB.login(u);
}
}
private static class EjbCL extends CustomCL {
public EjbCL(ClassLoader parent, String... selfFirsNames) {
super("Ejb", parent, selfFirsNames);
}
}
private static class SfWebCL extends CustomCL {
public SfWebCL(ClassLoader parent, String... selfFirsNames) {
super("SFWeb", parent, selfFirsNames);
}
}
public static void test1() throws Exception {
CustomCL ejbCL = new EjbCL(Demo.class.getClassLoader(), "com.test.zim.Demo$User",
"com.test.zim.Demo$LoginEJB");
CustomCL sfWebCL = new SfWebCL(ejbCL, "com.test.zim.Demo$User",
"com.test.zim.Demo$Servlet");
System.out.println("Logging in, self-first");
sfWebCL.loadClass("com.test.zim.Demo$Servlet", false).getMethod("doGet").invoke(null);
// sfWebCL.loadClass("com.test.zim.Demo$Servlet", false).getMethod("doGet");
// sfWebCL.loadClass("com.test.zim.Demo$User", false);
// sfWebCL.loadClass("com.test.zim.Demo$LoginEJB", false).getMethods();
System.out.println("Examining methods of LoginEJB");
ejbCL.loadClass("com.test.zim.Demo$LoginEJB", false).getMethods();
}
public static void main(String[] args) {
try {
test1();
} catch (Throwable e) {
e.printStackTrace(System.out);
}
}
运行测试代码后,输出为:
Logging in, self-first
SFWeb: Loading com.test.zim.Demo$Servlet in custom classloader
SFWeb: super.loadclass(java.lang.Object)
Ejb: super.loadclass(java.lang.Object)
SFWeb: Loading com.test.zim.Demo$User in custom classloader
SFWeb: super.loadclass(java.lang.System)
Ejb: super.loadclass(java.lang.System)
SFWeb: super.loadclass(java.lang.StringBuilder)
Ejb: super.loadclass(java.lang.StringBuilder)
SFWeb: super.loadclass(java.lang.Class)
Ejb: super.loadclass(java.lang.Class)
SFWeb: super.loadclass(java.io.PrintStream)
Ejb: super.loadclass(java.io.PrintStream)
Logging in with User loaded in SFWeb
SFWeb: super.loadclass(com.test.zim.Demo$LoginEJB)
Ejb: Loading com.test.zim.Demo$LoginEJB in custom classloader
Ejb: super.loadclass(java.lang.Object)
Ejb: super.loadclass(java.lang.System)
Ejb: super.loadclass(java.io.PrintStream)
LoginEJB loaded
Examining methods of LoginEJB
Ejb: already loaded(com.test.zim.Demo$LoginEJB)
Ejb: Loading com.test.zim.Demo$User in custom classloader
java.lang.LinkageError: loader constraint violation: loader (instance of com/test/zim/Demo$EjbCL) previously initiated loading for a different type with name "com/test/zim/Demo$User"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at com.test.zim.Demo$CustomCL.findClass(Demo.java:39)
at com.test.zim.Demo$CustomCL.loadClass(Demo.java:57)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
at java.lang.Class.privateGetPublicMethods(Class.java:2902)
at java.lang.Class.getMethods(Class.java:1615)
at com.test.zim.Demo.test1(Demo.java:117)
at com.test.zim.Demo.main(Demo.java:146)
我真的不明白为什么它显示链接错误,以及当我更改代码时
sfWebCL.loadClass("com.test.zim.Demo$Servlet", false).getMethod("doGet").invoke(null);
至
sfWebCL.loadClass("com.test.zim.Demo$Servlet", false).getMethod("doGet");
sfWebCL.loadClass("com.test.zim.Demo$User", false);
sfWebCL.loadClass("com.test.zim.Demo$LoginEJB", false).getMethods();
错误消失了,我有一些问题:
在我更改代码之前,linkageError 是如何发生的?我认为“com/test/zim/Demo$User”类应该在两个不同的类加载器中共存没有任何问题,因为这两个类加载器调用自己的defineClass方法?为什么错误说ejbClassLoader用不同的名称加载User类,我认为这是EJBClassLoader第一次加载User类?
更改代码后,我编写了一些代码来手动加载“User”和“LoginEJB”类,而不是调用“doGet()”方法,这两个代码片段有什么区别,为什么没有错误后面的代码?
错误发生在 ClassLoader.defineClass() 阶段,defineClass() 的真正含义是什么?
ClassLoader.findLoadedClass() 方法,是否意味着找到ClassLoader 曾经加载的类(例如Foo.class)?如果它的父 ClassLoader 之前已经加载了 Foo.class,它应该返回 true。但是如果子类加载器先加载Foo.class,父类加载器应该再加载一次,就会有两个Foo.class,它们共存没有问题吗?
解决方案
推荐阅读
- python - 使用灰度图像创建 BGR 图像
- angular - 获取颜色选择器的更改值
- amazon-web-services - 云铸造 API [ 组织和空间 ]
- c# - SQL 实体框架通过包含列选项的继承建立索引
- postgresql - Postgres pgAdmin 一次重命名多个列
- python - 在列表列表中查找重叠列表
- docker - 重新创建修改后的 Docker 映像
- javascript - 为什么我会收到一条错误消息,指出 onAuth 不是函数?
- python - 将 str 日期(来自数据库)与 QDateTimeEdit 值进行比较
- java - 在比较器中使用变量
传递给 PriorityQueue