首页 > 解决方案 > “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();

错误消失了,我有一些问题:

  1. 在我更改代码之前,linkageError 是如何发生的?我认为“com/test/zim/Demo$User”类应该在两个不同的类加载器中共存没有任何问题,因为这两个类加载器调用自己的defineClass方法?为什么错误说ejbClassLoader用不同的名称加载User类,我认为这是EJBClassLoader第一次加载User类?

  2. 更改代码后,我编写了一些代码来手动加载“User”和“LoginEJB”类,而不是调用“doGet()”方法,这两个代码片段有什么区别,为什么没有错误后面的代码?

  3. 错误发生在 ClassLoader.defineClass() 阶段,defineClass() 的真正含义是什么?

  4. ClassLoader.findLoadedClass() 方法,是否意味着找到ClassLoader 曾经加载的类(例如Foo.class)?如果它的父 ClassLoader 之前已经加载了 Foo.class,它应该返回 true。但是如果子类加载器先加载Foo.class,父类加载器应该再加载一次,就会有两个Foo.class,它们共存没有问题吗?

标签: javajvmclassloader

解决方案


推荐阅读