Создание нового экземпляра класса с помощью отражения с аргументами из нескольких загрузчиков классов

Здравствуйте, я пытаюсь создать новый экземпляр класса с помощью отражения:

В приведенном ниже примере применяется следующее:

  1. Это метод в подклассе Arg1
  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();
            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 21.11.2022 22:29

@Progman Я внес запрошенные изменения :)

AwesomeDude091 22.11.2022 07:20

Если код настолько частный и особенный, что вы не можете поделиться воспроизводителем, почему вы спрашиваете об этом публично? Это как хотеть съесть торт, но в то же время сохранить его.

kriegaex 29.11.2022 21:08

Почему у вас перевернуты дженерики? Итак, в этом методе у вас есть <T extends Arg2, S extends Arg1>, но в Foo они в правильном порядке <S extends Arg1, T extends Arg2>? Также Foo является параметризованным классом, поэтому добавление <?, ?> повысит читабельность. Однако предоставленный вами код буквально совсем не помогает, потому что явно проблема с инициализацией объектов в первую очередь, которой вы уделяете меньше внимания.

Mr.Typo 01.12.2022 13:05
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Как включить TLS в gRPC-клиенте и сервере : 2
Как включить TLS в gRPC-клиенте и сервере : 2
Здравствуйте! 🙏🏻 Надеюсь, у вас все хорошо и добро пожаловать в мой блог.
Сортировка hashmap по значениям
Сортировка hashmap по значениям
На Leetcode я решал задачу с хэшмапой и подумал, что мне нужно отсортировать хэшмапу по значениям.
Принципы SOLID - лучшие практики
Принципы SOLID - лучшие практики
SOLID - это аббревиатура, обозначающая пять ключевых принципов проектирования: принцип единой ответственности, принцип "открыто-закрыто", принцип...
gRPC на Android с использованием TLS
gRPC на Android с использованием TLS
gRPC - это относительно новая концепция взаимодействия между клиентом и сервером, но не более.
0
4
194
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Два класса с одинаковыми именами из разных загрузчиков классов во время выполнения считаются разными классами. [1]

Ошибка «несоответствие типа аргумента» говорит о том, что вы передаете экземпляр «Arg2» из одного загрузчика классов, но конструктору требуется экземпляр «Arg2» из другого загрузчика классов.

Вам нужно будет найти способ передать аргументы правильного класса, в том числе из правильного загрузчика классов, в конструктор. Вы не включили здесь достаточно контекста, чтобы я мог сказать, как вы можете этого достичь. ГЛ

дальнейшее чтение

[1] https://kepler-project.org/developers/teams/framework/design-docs/trade-studies/custom-class-loading/using-multiple-classloaders.html

Если разные версии одного и того же класса загружаются разными загрузчиками классов, то модули, использующие эти разные загрузчики классов, не могут обмениваться объектами этого типа.

Ответ принят как подходящий

Вы не можете использовать разные объекты в ClassLoaders. Класс Foo загружен из appClassLoaderFoo загружен из другого 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);
        }
    }

}

Спасибо, это решило мою проблему. Не думал об отражении в методах вместо конструктора

AwesomeDude091 02.12.2022 15:16

Другие вопросы по теме