Java向已编译类添加字段和方法,并使用类加载器重新加载

Java向已编译类添加字段和方法,并使用类加载器重新加载,java,instrumentation,java-bytecode-asm,bytecode-manipulation,java-assist,Java,Instrumentation,Java Bytecode Asm,Bytecode Manipulation,Java Assist,我想在编译的Java类中添加字段及其getter/setter,该类加载在Spring引导应用程序中。我能够使用JavaAssist和ASM修改该类。但问题是它不允许我在修改后重新加载类,因为它已经被加载了。我试图编写一个扩展java.lang.ClassLoader的类,但没有调用自定义ClassLoader。此外,我还检查了java的Instrumentation API,它清楚地说明 重传可能会改变方法体、常量池和 属性。重传不得添加、删除或重命名字段 或方法,更改方法的签名,或更改继承。

我想在编译的Java类中添加字段及其getter/setter,该类加载在Spring引导应用程序中。我能够使用JavaAssist和ASM修改该类。但问题是它不允许我在修改后重新加载类,因为它已经被加载了。我试图编写一个扩展java.lang.ClassLoader的类,但没有调用自定义ClassLoader。此外,我还检查了java的Instrumentation API,它清楚地说明

重传可能会改变方法体、常量池和 属性。重传不得添加、删除或重命名字段 或方法,更改方法的签名,或更改继承。 这些限制可能在将来的版本中取消。类文件 直到 如果生成的字节出错,则已应用转换 此方法将引发异常

你能告诉我如何做到这一点吗?我对运行时和编译时的修改持开放态度。如果你能分享一些很好的例子子类化可能不是一个选项,因为该类将由我们没有任何控制权的第三方jar使用,并且该jar将从类池中为我们提供该类。另外,你能告诉我如何使用自定义类加载器吗

技术

Java - JDK 8
Spring Boot - 2.x
Spring 5
Bytecode manipulation - ASM or JavaAssist
我想实现以下目标

尝试使用以下方法加载类时

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;
    }
}
公共类PIIClassLoader扩展类加载器{
静止的{
ClassLoader.registerAsParallelCapable();
}
/**
* 
*/
公共PIIClassLoader(){
超级();
}
/**
*@param父级
*/
公共类加载器(类加载器父级){
超级(家长);
}
@凌驾
公共类loadClass(字符串名称)引发ClassNotFoundException{
返回loadClass(名称,false);
}
@凌驾
受保护类loadClass(字符串名称,布尔解析)
抛出ClassNotFoundException{
//尊重java.*包。
if(name.startsWith(“java”)){
返回super.loadClass(名称、解析);
}
否则{
//看看我们是否已经加载了类。
if(Foo.class.getName().equals(name)){
返回null;
}
c类=findLoadedClass(名称);
如果(c!=null)返回c;
}
返回null;
}
}

这似乎是不可能的,但也许有某种方法可以做到,但我不明白。如果要添加或删除字段,此类型的现有对象会发生什么情况?在我看来,如果您需要这种功能,可以使用JavaScript/ScriptEngine。这是一个javascript中的管理单元。由于JVM不支持此类更改,因此存在检测API中的限制。
ClassLoader
根本不支持更改已加载类的定义。如果是这样的话,我们就不需要插装API了……有没有办法阻止类加载器加载文件?这样代码就可以修改类并加载它。如果您使用的是指令插入(例如javaagent),任何类的字节码都会在最初加载之前传递给您。您可以根据需要对其进行更改,并返回已加载的修改字节码。(查看并实现ClassFileTransformer接口)这似乎是不可能的,但也许有某种方法可以做到,我不理解。如果要添加或删除字段,此类型的现有对象会发生什么情况?在我看来,如果您需要这种功能,可以使用JavaScript/ScriptEngine。这是一个javascript中的管理单元。由于JVM不支持此类更改,因此存在检测API中的限制。
ClassLoader
根本不支持更改已加载类的定义。如果是这样的话,我们就不需要插装API了……有没有办法阻止类加载器加载文件?这样代码就可以修改类并加载它。如果您使用的是指令插入(例如javaagent),任何类的字节码都会在最初加载之前传递给您。您可以根据需要对其进行更改,并返回已加载的修改字节码。(请参阅并实现ClassFileTransformer接口)
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;
    }
}