Dieses Blog durchsuchen

Wird geladen...

Sonntag, 13. März 2011

Inject java code in existing classes at runtime

This post described how to inject any java code in existing classes at runtime. It is based on my "Example project of how to create a generic Logging Agent with Java Intrumentation API and Javassist (maven project on github)".

In many situations is it helpful to add own functionality to running (production) code - for debugging/tracing purpose or anything else.

In this case we can use the Java Intrumentation API and Javassist to add our functionalities at runtime. The Java Intrumentation API give us a hook point to instrument any loaded classes. Javassist is a bytecode manipulation library, it can modify and enhance any java bytecode.

The agent implementation class of an "Java Intrumentation API implementation" must have a premain()-Method (registered by the Premain-Class-Attribut of the MANIFEST.MF-File). This method is called by the java runtime at startup. And now we can use the Instrumentation-Instance to register a own ClassFileTransformer.
public static void premain(String agentArgs, Instrumentation instrumentation) {
  instrumentation.addTransformer( new LoggerAgent() );
}
The ClassFileTransformer#transform() method is called for any class loaded by the java runtime.
byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
So now we can start with Javassist to modify/enhance the loaded class and inject some fields/methods and so on. First we must load the bytecode and build a Javasisst class instance, then we can add some fields and/or code that is invoked before a method is called or after a method is called.
ClassPool pool = ClassPool.getDefault();
CtClass cl = pool.makeClass( new java.io.ByteArrayInputStream( b ) );
if( cl.isInterface() == false ) {
 // adds a class field
 CtField field = CtField.make("String myField", cl );
 cl.addField( field, "my initial value" );

 CtBehavior[] methods = cl.getDeclaredBehaviors();
 for( CtBehavior[] method : methods ) {
  if( method.isEmpty() == false ) {
   // inject some code before/after a method is called
   method.insertBefore( "System.out.println(\"called before\");" );
   method.insertAfter( "System.out.println(\"called after\");" );
  }
 }
 b = cl.toBytecode();
}

return b;
So now we are able to build the agent jar. In the maven project it use the maven-assembly-plugin to create a "fat jar" that includes also the dependencies to Javasisst and slf4j. Simply run mvn clean package and take the "fat jar" from the /target folder.

Now start the java runtime command line with the javaagent attribut:
java -javaagent:loggingagent-0.0.1-SNAPSHOT-jar-with-dependencies.jar ...

Keine Kommentare:

Kommentar veröffentlichen