Причина «java.lang.LinkageError: ранее инициированная загрузка для другого типа с именем» - PullRequest
0 голосов
/ 18 декабря 2018

Я прочитал пост 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?загрузить классы «User» и «LoginEJB» вручную, вместо вызова метода «doGet ()», в чем разница между двумя фрагментами кода, почему в последнем коде нет ошибок?

  2. Ошибка произошла на этапе ClassLoader.defineClass (), что на самом деле означает defineClass ()?

  3. Метод ClassLoader.findLoadedClass (), значит ли это найти класс (например, Foo.class), который когда-либо загружал ClassLoader?Если его родительский ClassLoader загружал Foo.class ранее, он должен вернуть true.Но если дочерний загрузчик классов сначала загрузит Foo.class, родительский загрузчик классов должен загрузить его снова, и будет два Foo.class, и они будут сосуществовать без проблем?

...