I have a file TestInstrumentation.java as below -
public class TestInstrumentation {
public static void main(String args[]) throws InterruptedException {
Lion l = new Lion();
l.runLion();
}
}
There is a class Lion.java as below -
public class Lion {
String str = "abc";
public void runLion() throws InterruptedException {
System.out.println("Lion is going to run........");
}
}
And there is another class LionExt.java as follows -
public class LionExt extends Lion {
String str = "xyz";
String str1 = "xyz";
@Override
public void runLion() throws InterruptedException {
//super.runLion();
System.out.println("LionExt is going to run........" + str + "--" + str1 + "++");
sayHello();
}
public void sayHello() {
System.out.println("OK");
}
}
I want the line Lion l = new Lion(); in TestInstrumentation.java to return an instance of TestExt instead of Test without changing the source code of TestInstrumentation.java
This means I need to use some library to dynamically load the corresponding subclass of the Test class instead of the class itself which creating the new instance.
To do this I have used Java instrumentation API and Javaassist API and my Instrumentation transformer class looks like below -
public class DurationTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className,
Class classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] byteCode = classfileBuffer;
if (className.equals("com/javapapers/java/instrumentation/Lion")) {
System.out.println("Instrumenting......");
try {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClassold = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
CtClass ctClassnew = classPool.getCtClass("com.javapapers.java.instrumentation.LionExt");
CtField[] fields = ctClassnew.getDeclaredFields();
for(CtField field : fields) {
if(!Arrays.asList(Arrays.stream(ctClassold.getDeclaredFields()).map(m -> m.getName()).toArray()).contains(field.getName()))
ctClassold.getClassFile2().addField2(field.getFieldInfo2());
}
CtMethod[] methods = ctClassnew.getDeclaredMethods();
for (CtMethod method : methods) {
if(Arrays.asList(Arrays.stream(ctClassold.getDeclaredMethods()).map(m -> m.getName()).toArray()).contains(method.getName()))
ctClassold.getDeclaredMethod(method.getName()).setBody(method, new ClassMap());
else ctClassold.getClassFile2().addMethod2(method.getMethodInfo2());
}
byteCode = ctClassold.toBytecode();
ctClassold.detach();
System.out.println("Instrumentation complete.");
} catch (Throwable ex) {
System.out.println("Exception: " + ex);
ex.printStackTrace();
}
}
return byteCode;
}
}
And I have added the transformer in the premain method in the agent class -
public class DurationAgent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("Executing premain.........");
inst.addTransformer(new DurationTransformer());
}
}
All I have done in the overridden transform method is to copy all the methods and the fields of the subclass to the superclass and below is the output of the file -
Executing premain.........
Instrumenting......
Instrumentation complete.
LionExt is going to run........abc--null++
OK
So it works! except not quite!
You see that overwriting the superclass with subclass method body is not a good idea and you cannot use the super() call in any of the subclass methods and the values of the fields are not taking effect.
Is there any other way to do what I want to do using this or anyother library?
Aucun commentaire:
Enregistrer un commentaire