Здравствуйте, я пытаюсь создать новый экземпляр класса с помощью отражения:
В приведенном ниже примере применяется следующее:
public <T extends Arg2, S extends Arg1> Foo
getFoo(@NotNull Data<T, S> data) {
Class<?>[] classes = new Class<?>[]{data.getArg1(), data.getArg2()};
T entity = getArg2(data);
try {
Class<? extends Foo> clazz = data.getFoo();
Constructor<? extends Foo> constructor = clazz.getDeclaredConstructor(classes);
constructor.setAccessible(true);
Object[] objects = new Object[]{this, entity};
return constructor.newInstance(objects);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException |
ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
Этот код работает, когда предоставленные аргументы поступают из одного и того же загрузчика классов, но код дает сбой, когда аргументы поступают из разных загрузчиков классов. Таким образом, несколько загрузчиков классов в качестве аргументов приводят к сбою метода.
Есть ли способ заставить Java принимать мои аргументы от нескольких загрузчиков классов?
Редактировать: Причина, по которой у меня есть несколько загрузчиков классов, связана с тем, что я загружаю в приложение внешние файлы jar, которые были скомпилированы для этого API приложений, с помощью пользовательского URLClassLoader
.
Что касается минимального воспроизводимого примера, я не могу в настоящее время привести пример, поскольку это частный код, которым я не владею. Владелец кода должен был бы дать мне явное разрешение на загрузку такого обширного фрагмента кода (это набор классов, которые по сути являются краеугольным камнем всего приложения). Я могу и буду запускать любые предложения и направлю этот пост владельцу на одобрение.
Любая помощь высоко ценится :)
Редактировать 2:
Вот код с отладочными сообщениями:
public <T extends Arg2, S extends Arg1> Foo
getFoo(@NotNull Data<T, S> data) {
Class<?>[] classes = new Class<?>[]{data.getArg1(), data.getArg2()};
T entity = getArg2(data);
try {
Class<? extends Foo> clazz = data.getFoo();
System.out.println(clazz.getClassLoader());
Constructor<? extends Foo> constructor = clazz.getDeclaredConstructor(classes);
constructor.setAccessible(true);
System.out.println(this.getClass().getClassLoader());
System.out.println(entity.getClassLoader());
Object[] objects = new Object[]{this, entity};
return constructor.newInstance(objects);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException |
ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
Учитывая, что Arg1, Arg2 и Foo являются классами, которые являются частью базового приложения, вывод будет следующим:
jdk.internal.loader.ClassLoaders$AppClassLoader@1d44bcfa
jdk.internal.loader.ClassLoaders$AppClassLoader@1d44bcfa
jdk.internal.loader.ClassLoaders$AppClassLoader@1d44bcfa
Учитывая, что Arg1 и Foo являются классами из одного и того же внешнего файла Jar, а Arg2 остается классом, который является частью базового приложения:
Class Loader for a single jar file
Joined Class Loader for all jar files
jdk.internal.loader.ClassLoaders$AppClassLoader@1d44bcfa
java.lang.IllegalArgumentException: argument type mismatch
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480) ~[na:na]
Примечание. Это единственные два варианта использования.
@Progman Я внес запрошенные изменения :)
Если код настолько частный и особенный, что вы не можете поделиться воспроизводителем, почему вы спрашиваете об этом публично? Это как хотеть съесть торт, но в то же время сохранить его.
Почему у вас перевернуты дженерики? Итак, в этом методе у вас есть <T extends Arg2, S extends Arg1>
, но в Foo они в правильном порядке <S extends Arg1, T extends Arg2>
? Также Foo является параметризованным классом, поэтому добавление <?, ?> повысит читабельность. Однако предоставленный вами код буквально совсем не помогает, потому что явно проблема с инициализацией объектов в первую очередь, которой вы уделяете меньше внимания.
Два класса с одинаковыми именами из разных загрузчиков классов во время выполнения считаются разными классами. [1]
Ошибка «несоответствие типа аргумента» говорит о том, что вы передаете экземпляр «Arg2» из одного загрузчика классов, но конструктору требуется экземпляр «Arg2» из другого загрузчика классов.
Вам нужно будет найти способ передать аргументы правильного класса, в том числе из правильного загрузчика классов, в конструктор. Вы не включили здесь достаточно контекста, чтобы я мог сказать, как вы можете этого достичь. ГЛ
Если разные версии одного и того же класса загружаются разными загрузчиками классов, то модули, использующие эти разные загрузчики классов, не могут обмениваться объектами этого типа.
Вы не можете использовать разные объекты в ClassLoaders
. Класс Foo
загружен из app
ClassLoader
≠ Foo
загружен из другого ClassLoader
экземпляра. См. следующий пример кода:
import com.google.gson.Gson;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
public class Test {
public static void main(String[] args) throws Exception {
var customClassLoader = new CustomClassLoader();
////////////////////// normal reflection //////////////////////
Test.class.getMethod("foo", Foo.class).invoke(null, new Foo()); // equivalent to foo(new Foo);
////////////////////// instantiating a new Foo obj from a custom class loader //////////////////////
var foo = customClassLoader.findClass(Foo.class.getName()).getDeclaredConstructor().newInstance();
try {
////////////////////// calling foo by passing a Foo obj from different ClassLoader //////////////////////
Test.class.getMethod("foo", Foo.class).invoke(null, foo); // yields java.lang.IllegalArgumentException: argument type mismatch!
} catch (IllegalArgumentException e) {
System.err.println(e);
}
////////////////////// workaround, using gson to serialize the obj //////////////////////
var gson = new Gson();
Foo serializedFoo = gson.fromJson(gson.toJson(foo), Foo.class);
Test.class.getMethod("foo", Foo.class).invoke(null, serializedFoo); // no exception
}
public static void foo(Foo foo) {
System.out.println("Test#foo: " + foo.getClass().getClassLoader().getName());
}
public static class Foo {
}
public static class CustomClassLoader extends ClassLoader {
public CustomClassLoader() {
super("custom", getSystemClassLoader());
}
@Override
public Class<?> findClass(String name) throws ClassFormatError {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(name.replace('.', File.separatorChar) + ".class");
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue;
try {
while ((nextValue = inputStream.read()) != -1) byteStream.write(nextValue);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
var data = byteStream.toByteArray();
return defineClass(name, data, 0, data.length);
}
}
}
В зависимости от вашего использования это может быть не самое эффективное решение, но оно всегда будет работать. Лучшим решением было бы использование отражения, но вам придется делать все с отражением. Что-то вроде этого:
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
public class Test {
public static void main(String[] args) throws Exception {
var customClassLoader = new CustomClassLoader();
Test.class.getMethod("foo", Object.class).invoke(null, new Foo());
var foo = customClassLoader.findClass(Foo.class.getName()).getDeclaredConstructor().newInstance();
Test.class.getMethod("foo", Object.class).invoke(null, foo);
}
public static void foo(Object foo) throws Exception {
if (foo.getClass().getClassLoader() instanceof CustomClassLoader) {
foo.getClass().getMethod("sayFoo").invoke(foo);
} else {
((Foo) foo).sayFoo();
}
}
public static class Foo {
public void sayFoo() {
System.out.println("Foo");
}
}
public static class CustomClassLoader extends ClassLoader {
public CustomClassLoader() {
super("custom", getSystemClassLoader());
}
@Override
public Class<?> findClass(String name) throws ClassFormatError {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(name.replace('.', File.separatorChar) + ".class");
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue;
try {
while ((nextValue = inputStream.read()) != -1) byteStream.write(nextValue);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
var data = byteStream.toByteArray();
return defineClass(name, data, 0, data.length);
}
}
}
Спасибо, это решило мою проблему. Не думал об отражении в методах вместо конструктора
Пожалуйста, отредактируйте свой вопрос, чтобы включить полное сообщение об ошибке, которое вы получите. Также добавьте минимально воспроизводимый пример вашей проблемы к вашему вопросу, чтобы было легче воспроизвести проблему. Также добавьте подробное описание того, откуда берутся ваши разные загрузчики классов и почему у вас нет только одного.