Java Добавление поля и метода в скомпилированный класс и перезагрузка с помощью загрузчика классов

Я хотел бы добавить поле вместе с его получателем / установщиком в скомпилированные классы Java, которые загружаются в приложение загрузки Spring. Мне удалось изменить класс с помощью JavaAssist и ASM. Но проблема в том, что это не позволяет мне перезагрузить класс после модификации, поскольку он уже загружен. Я попытался написать класс, расширяющий java.lang.ClassLoader, но пользовательский загрузчик классов не вызывается. Кроме того, я проверил API-интерфейс Java Instrumentation, в котором четко указано

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

Не могли бы вы сообщить мне, как этого добиться? Я открыт для модификации времени выполнения и времени компиляции. Если вы можете поделиться некоторыми примерами, это будет здорово. Создание подклассов не может быть вариантом, потому что этот класс будет использоваться сторонними jar-файлами, над которыми у нас нет никакого контроля, и этот jar-файл будет использовать класс из пула классов. Кроме того, не могли бы вы сообщить мне, как использовать собственный загрузчик классов?

Технологии

Java - JDK 8
Spring Boot - 2.x
Spring 5
Bytecode manipulation - ASM or JavaAssist

Я бы хотел добиться ниже

От

class A {
 Integer num;
}

Чтобы

class A {
    Integer num;
    //Newly added field
    private String numModified; 
    //Newly added method
    public String getNumModified(){}
    public String setNumModified(String numModified){}
}

При попытке загрузить класс с помощью следующих методов

private static Class loadClass(byte[] b,String className) {
        // Override defineClass (as it is protected) and define the class.
        Class clazz = null;
        try {
            ClassLoader loader = ClassLoader.getSystemClassLoader();
            Class cls = Class.forName("java.lang.ClassLoader");
            java.lang.reflect.Method method =
                    cls.getDeclaredMethod(
                            "defineClass", 
                            new Class[] { String.class, byte[].class, int.class, int.class });

            // Protected method invocation.
            method.setAccessible(true);
            try {
                Object[] args = 
                        new Object[] { className, b, new Integer(0), new Integer(b.length)};
                clazz = (Class) method.invoke(loader, args);
            } finally {
                method.setAccessible(false);
            }
        } catch (Exception e) {
            e.printStackTrace();
            //System.exit(1);
        }
        return clazz;
    }

Исключение

java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at com.test.pii.mask.util.ClassModifier.loadClass(ClassModifier.java:110)
    at com.test.pii.mask.util.ClassModifier.modifyClass(ClassModifier.java:85)
    at com.test.pii.mask.util.ClassModifier.main(ClassModifier.java:200)
Caused by: java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "com/test/pii/web/dto/SomeOther"
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    ... 7 more

Пользовательский загрузчик классов, который не вызывается

public class PIIClassLoader extends ClassLoader {

    static {
        ClassLoader.registerAsParallelCapable();
    }

    /**
     * 
     */
    public PIIClassLoader() {
        super();
    }

    /**
     * @param parent
     */
    public PIIClassLoader(ClassLoader parent) {
        super(parent);
    }


    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        // respect the java.* packages.
        if( name.startsWith("java.")) {
            return super.loadClass(name, resolve);
        }
        else {
            // see if we have already loaded the class.
            if(Foo.class.getName().equals(name)) {
                return null;
            }

            Class<?> c = findLoadedClass(name);
            if( c != null ) return c;
        }
        return null;
    }
}

0 ответов

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