Different sources offer different ways to implement ClassFileTransformer
with Javassist:
blog.newrelic.com/2014/09/29/diving-bytecode-manipulation-creating-audit-log-asm-javassist/
public byte[] transform(...) {
ClassPool pool = ClassPool.getDefault(); // Edited to simplify
pool.insertClassPath(new ByteArrayClassPath(className, classfileBuffer));
CtClass cclass = pool.get(className.replaceAll("/", "."));
...
return cclass.toBytecode();
}
blog.javabenchmark.org/2013/05/java-instrumentation-tutorial.html
public byte[] transform(...) {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("org.javabenchmark.instrumentation.Sleeping");
...
return cc.to:byteCode(); // edited to simplify
}
http://javapapers.com/core-java/java-instrumentation/
public byte[] transform(...) {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
...
return ctClass.to:byteCode(); // edited to simplify
}
Which is most correct way and why? Is there other solutions which are better than those three?
https://stackoverflow.com/a/26725215/776884 mentions settings correct classloader. Is it required when using instrumentation API? Should ClassPool classPool = new ClassPool()
with CtClass.makeClass()
be used with instumentation API?
All of the examples are wrong and will not work in a general setup. You should never use the default class pool and you should never - such as shown in the new relic blog - share a class pool between transformers as you cannot tell if loaded classes are related by their class loader.
Consider an application server where ever app has its own class loader; you could not even see the transformed classes using the default class pool (which references the system class loader, i.e. the class path) and you cannot promise that all apps on the app server run the same version of some class if they both contain them.
The only correct solution is:
ClassPool classPool = new ClassPool();
classPool.appendClassPath(new LoaderClassPath(loader));
CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
This way you take into account that every class is loaded by a different class loader which can represent different views, i.e. contain different classes or a different version of the class than the class path and you still parse the provided byte array which can contain new members that were added by previously triggered transformers by other Java agents.