java.lang.LinkageError при вызове метода класса через MethodHandles дважды
UPD: Цель данного кода - в режиме выполнения загружать классы из файлов, находящихся рядом с исполняемой программой. При этом один и тот же класс должен быть загружен много раз(есстественно не одним и тем же classLoader'ом) и каждый раз загружаются разные версии этого класса. Загрузить один раз и дальше использовать нельзя.
Привожу ниже упрощённый минимальный код для воспроизведения проблемы
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Main {
public static void main(String[] args) throws Throwable
{
method(); //OK
method(); //FAIL
}
public static void method() throws Throwable
{
DefineClassLoader classLoader = new DefineClassLoader(new URL[0], ClassLoader.getSystemClassLoader());
byte[] clazzBytes = Files.readAllBytes(Paths.get("TestedClass.class"));
Class<?> clazz = classLoader.rawDefine("TestedClass", clazzBytes);
Object object = MethodHandles.publicLookup().findStatic(clazz, "getDefault", MethodType.methodType(clazz)).invoke();
System.out.println(object.getClass().getName());
}
}
import java.net.URL;
import java.net.URLClassLoader;
public class DefineClassLoader extends URLClassLoader {
public DefineClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public Class<?> rawDefine(String name, byte[] bytes)
{
return defineClass(name, bytes, 0, bytes.length);
}
}
Еще понадобится ОТДЕЛЬНО скомплировать класс TestedClass через javac
public class TestedClass {
public String a;
public static TestedClass getDefault()
{
TestedClass ret = new TestedClass();
ret.a = "OK";
return ret;
}
}
На втором вызове метода вылетает следующее исключение:
Exception in thread "main" java.lang.IllegalAccessException: no such method: TestedClass.getDefault()TestedClass/invokeStatic
at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:959)
at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1101)
at java.base/java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:2055)
at java.base/java.lang.invoke.MethodHandles$Lookup.findStatic(MethodHandles.java:1112)
at Main.method(Main.java:18)
at Main.main(Main.java:11)
Caused by: java.lang.LinkageError: loader constraint violation: when resolving method 'TestedClass TestedClass.getDefault()' the class loader 'bootstrap' of the current class, java/lang/Object, and the class loader DefineClassLoader @681a9515 for the method's defining class, TestedClass, have different Class objects for the type TestedClass used in the signature (java.lang.Object is in module java.base of loader 'bootstrap'; TestedClass is in unnamed module of loader DefineClassLoader @681a9515, parent loader 'app')
at java.base/java.lang.invoke.MethodHandleNatives.resolve(Native Method)
at java.base/java.lang.invoke.MemberName$Factory.resolve(MemberName.java:1070)
at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1098)
... 4 more
Поскольку исключение выше немного запутно из за модулей Java 9+ приведу исключение на JRE 8
Exception in thread "main" java.lang.IllegalAccessException: no such method: TestedClass.getDefault()TestedClass/invokeStatic
at java.lang.invoke.MemberName.makeAccessException(MemberName.java:867)
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1003)
at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:1386)
at java.lang.invoke.MethodHandles$Lookup.findStatic(MethodHandles.java:780)
at Main.method(Main.java:18)
at Main.main(Main.java:11)
Caused by: java.lang.LinkageError: loader constraint violation: when resolving method "TestedClass.getDefault()LTestedClass;" the class loader (instance of <bootloader>) of the current class, java/lang/Object, and the class loader (instance of DefineClassLoader) for the method's defining class, TestedClass, have different Class objects for the type TestedClass used in the signature
at java.lang.invoke.MethodHandleNatives.resolve(Native Method)
at java.lang.invoke.MemberName$Factory.resolve(MemberName.java:975)
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1000)
... 4 more
Что происходит: при каждом вызове метода создается новый ClassLoader, читаются байты класса из файла, класс дефайнится и с помощью MethodHandles я пытаюсь вызвать необходимый мне статический метод
Первый раз проходит успешно - JVM загружает класс впервые и никаких конфликтов это не вызывает
Второй раз ошибка, которой быть не должно. Класслоадер, созданный на предыдущем этапе по идее никак не должен влиять на работу текущего, так как и у первого, и у второго класслоадера родитель SystemClassLoader. Перечитав документацию по classloader так и не могу понять почему такое возникает и как это исправить
Сам defineClass как можно видеть по логу работает без ошибок - проблемы только с вызовом этого метода через MethodHandles.
UPD: Использование ещё одного класслоадера(с другим именем класса) не решило проблему