我想在 Spring Boot 应用程序中加载的已编译 Java 类中添加字段及其 getter/setter。我能够使用 JavaAssist 和 ASM 修改该类。但问题是它不允许我在修改后重新加载类,因为它已经被加载了。我试图编写一个扩展 java.lang.ClassLoader 的类,但没有调用自定义类加载器。另外,我检查了明确指出的 java 的 Instrumentation API
重新转换可能会改变方法体、常量池和属性。重新转换不得添加、删除或重命名字段或方法,更改方法的签名或更改继承。这些限制可能会在未来的版本中取消。在应用转换之前,不会检查、验证和安装类文件字节,如果结果字节有误,此方法将引发异常。
你能告诉我如何实现这一目标吗?我对运行时与编译时修改持开放态度。如果你能分享一些很好的例子。子类化可能不是一个选项,因为此类将由我们没有任何控制权的第三方 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;
}
}