问题
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