Java Adding field and method to compiled class and reload using class loader

那年仲夏 提交于 2020-06-11 11:43:09

问题


I would like to add field along with its getter/setter in compiled Java classes which is loaded in a Spring boot application. I was able to modify the class using JavaAssist and ASM. But the problem is it is not letting me reload the class after modification, since this is already been loaded. I tried to write a class extending java.lang.ClassLoader but custom classloader is not getting called. Also, I checked java's Instrumentation API which clearly states

The retransformation may change method bodies, the constant pool and attributes. The retransformation must not add, remove or rename fields or methods, change the signatures of methods, or change inheritance. These restrictions maybe be lifted in future versions. The class file bytes are not checked, verified and installed until after the transformations have been applied, if the resultant bytes are in error this method will throw an exception.

Could you please let me know how to achieve this? I am open to runtime vs compile time modification. If you can share some examples that will be great. Subclassing may not be an option because this class will be used by third-party jars on which we don't have any control and this jar will us the class from the class pool. Also, could you please let me know how to use custom class loader?

Technology

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

I would like to achieve below

From

class A {
 Integer num;
}

To

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

When trying to load the class using below methods

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;
    }

Exception

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

Custom Class Loader which is not getting called

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;
    }
}

来源:https://stackoverflow.com/questions/61902600/java-adding-field-and-method-to-compiled-class-and-reload-using-class-loader

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!