Reputation: 1213
I have a class in the source code
class BillPughSingleton {
private BillPughSingleton() {
System.out.println("BillPughSingleton private constructor called");
}
// Lớp nội bộ static chứa instance duy nhất của Singleton
private static class SingletonHelper {
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
Compiling works fine!!
Now I want to override the jvm bytecode to add serializable capability to this class by using Instrumentation API and Javassist
public class Agent {
private static final Logger LOGGER = LoggerFactory.getLogger(Agent.class);
private static Integer count = 0;
public static void premain(String agentArgs, Instrumentation inst) {
LOGGER.info("[Agent] In premain method");
String className = "org.example.genericapplicationeventlistener.BillPughSingleton";
transformClass2( className, inst );
}
private static void transformClass2(String className, Instrumentation inst) {
System.out.println(count);
Class<?> targetCls = null;
ClassLoader targetClassLoader = null;
// see if we can get the class using forName
try {
targetCls = Class.forName(className);
targetClassLoader = targetCls.getClassLoader();
transform2(targetCls, targetClassLoader, inst);
return;
} catch (Exception ex) {
LOGGER.error("Class [{}] not found with Class.forName", className);
}
// otherwise iterate all loaded classes and find what we want
for (Class<?> clazz : inst.getAllLoadedClasses()) {
if (clazz.getName().equals(className)) {
targetCls = clazz;
targetClassLoader = targetCls.getClassLoader();
transform2(targetCls, targetClassLoader, inst);
return;
}
}
throw new RuntimeException("Failed to find class [" + className + "]");
}
private static void transform2(Class<?> targetCls, ClassLoader targetClassLoader, Instrumentation inst) {
SerializableAdder dt = new SerializableAdder(targetCls.getName(), targetClassLoader);
inst.addTransformer(dt, true);
try {
inst.retransformClasses(targetCls);
} catch (Exception ex) {
throw new RuntimeException("Transform failed for class: [" + targetCls.getName() + "]", ex);
}
}
static class SerializableAdder implements ClassFileTransformer {
private final String targetClassName;
/** The class loader of the class we want to transform */
private final ClassLoader targetClassLoader;
SerializableAdder(String targetClassName, ClassLoader targetClassLoader) {
this.targetClassName = targetClassName;
this.targetClassLoader = targetClassLoader;
}
@Override
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
byte[] byteCode = classfileBuffer;
String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/"); // replace . with /
if (!className.equals(finalTargetClassName)) {
return byteCode;
}
if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get(targetClassName);
if (ctClass.isFrozen()) {
ctClass.defrost();
}
ClassFile classFile = ctClass.getClassFile();
if ( !Set.of(classFile.getInterfaces()).contains( "java.io.Serializable" ) ) {
classFile.addInterface( "java.io.Serializable" );
}
byteCode = ctClass.toBytecode();
System.out.println();
} catch (Exception e) {
e.printStackTrace();
}
}
return byteCode;
}
}
}
return byteCode;
But i got this
Exception in thread "main" java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:574)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:491)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:503)
Caused by: java.lang.RuntimeException: Transform failed for class: [org.example.genericapplicationeventlistener.BillPughSingleton]
at com.aeris.changelog.spring.instrumentation.Agent.transform2(Agent.java:204)
at com.aeris.changelog.spring.instrumentation.Agent.transformClass2(Agent.java:191)
at com.aeris.changelog.spring.instrumentation.Agent.premain(Agent.java:101)
... 6 more
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to change superclass or interfaces
at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:169)
at com.aeris.changelog.spring.instrumentation.Agent.transform2(Agent.java:202)
... 8 more
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message Outstanding error when calling method in invokeJavaAgentMainMethod at s\src\java.instrument\share\native\libinstrument\JPLISAgent.c line: 619
*** java.lang.instrument ASSERTION FAILED ***: "success" with message invokeJavaAgentMainMethod failed at s\src\java.instrument\share\native\libinstrument\JPLISAgent.c line: 459
*** java.lang.instrument ASSERTION FAILED ***: "result" with message agent load/premain call failed at s\src\java.instrument\share\native\libinstrument\JPLISAgent.c line: 422
Fatal error: processing of -javaagent failed, processJavaStart failed
This is for learning purpose, so please ignore any stupidity!!
Was I wrong somewhere? Please help!!
UPDATE: Code changed according to rzwitserloot
public class Agent {
private static final Logger LOGGER = LoggerFactory.getLogger(Agent.class);
private static Integer count = 0;
public static void premain(String agentArgs, Instrumentation inst) {
LOGGER.info("[Agent] In premain method");
String className = "org.example.genericapplicationeventlistener.BillPughSingleton";
transformClass2( className, inst );
}
private static void transformClass2(String className, Instrumentation inst) {
Class<?> targetCls = null;
ClassLoader targetClassLoader = null;
// see if we can get the class using forName
try {
targetCls = Class.forName(className);
targetClassLoader = targetCls.getClassLoader();
transform2(targetCls, targetClassLoader, inst);
} catch (Exception ex) {
LOGGER.error("Class [{}] not found with Class.forName", className);
ex.printStackTrace();
}
}
private static void transform2(Class<?> targetCls, ClassLoader targetClassLoader, Instrumentation inst) {
SerializableAdder dt = new SerializableAdder(targetCls.getName(), targetClassLoader);
inst.addTransformer(dt, true);
try {
inst.retransformClasses(targetCls);
} catch (Exception ex) {
throw new RuntimeException("Transform failed for class: [" + targetCls.getName() + "]", ex);
}
}
static class SerializableAdder implements ClassFileTransformer {
private final String targetClassName;
/** The class loader of the class we want to transform */
private final ClassLoader targetClassLoader;
SerializableAdder(String targetClassName, ClassLoader targetClassLoader) {
this.targetClassName = targetClassName;
this.targetClassLoader = targetClassLoader;
}
@Override
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
byte[] byteCode = classfileBuffer;
String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/"); // replace . with /
if (!className.equals(finalTargetClassName)) {
return byteCode;
}
if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get(targetClassName);
if (ctClass.isFrozen()) {
ctClass.defrost();
}
ClassFile classFile = ctClass.getClassFile();
if ( !Set.of(classFile.getInterfaces()).contains( "java.io.Serializable" ) ) {
classFile.addInterface( "java.io.Serializable" );
}
byteCode = ctClass.toBytecode();
System.out.println();
} catch (Exception e) {
e.printStackTrace();
}
}
return byteCode;
}
}
}
In short, removed the for loop. Still the same error!!
Please help
Upvotes: 2
Views: 105
Reputation: 103608
You are using the 'retransform' / 'reload' functionality for no reason. What you want to do, is inspect all classes as they are loaded and transform them if it is the one you are interested in. If you call reload/retransform/'re' anything, you've messed up. There is no need and it causes the errors you are seeing.
To do this, you call instrumentation.addTransformer
in your agentmain
(just the one line). The transformer you pass is your own - it is invoked for everything. The first line it should have is 'is this BillPughClass'? If no, just return null which indicates you don't want to transform it.
Otherwise apply your bytecode modification magic and voila.
Upvotes: -1