Introduction to Java Agents
In this article we will discuss about Java Agents. Java Agents are software components that provide instrumentation capabilities to an application. In the context of agents, instrumentation provides the capability of re-defining the content of class that is loaded at run-time. We will discuss this in more detail in the further sections.
Download Source Code: [download >
Writing the first agent
package net.javabeat.articles.javaagent.test; import java.lang.instrument.Instrumentation; public class TestJavaAgent < public static void premain(String agentArgument, Instrumentation instrumentation)< System.out.println('Test Java Agent'); >>
Note that it is mandatory for agents to have the above method with the exact signature. Note that the above agent does nothing another that printing sample content to the console.
Manifest-Version: 1.0 Premain-Class: net.javabeat.articles.javaagent.test.TestJavaAgent
Agents are packaged as jar files and are made visible to the application using the option ‘javaagent’. Note that it is essential for the jar file to have the Premain-Class attribute that specifies the fully qualified name of the agent class containing the premain method. Now that we have written an agent, we have to make use of the agent. For making use of the agent, let us create a sample java class as follows. Note that nowhere in the code, we are invoking the agent. Instead while running the application, agents are specified as virtual machine arguments.
package net.javabeat.articles.javaagent.test; public class TestMain < public static void main(String[] args) < System.out.println('Test Main Class'); >>
- Create a jar file called ‘test-agent.jar’ by packaging the class file ‘net.javabeat.articles.javaagent.test.TestJavaAgent’ and the manifest file ‘MANIFEST.MF’
- Make use of the command, to run the agent, java -javaagent:test-agent.jar net.javabeat.articles.javaagent.test.TestMain
The above command will produce the below output,
Test Java Agent Test Main Class
Note that there are several things to be noted in the above section. First of all, the agent to be run is passed as virtual machine parameter to the application. This is done through the option ‘javaagent’, what follows after that is ‘:’ followed by the path to the jar file. Note that agent classes are invoked first before the above application classes, this can be seen from the output produced by the sample program.
Writing a simple Agent
Now that we have familiarized with the several aspects involved in writing a simple agent, let us start adding some more functionality to the agent in this section. Consider the below code snippet representing the agent,
package net.javabeat.articles.javaagent.simple; import java.lang.instrument.Instrumentation; public class SimpleAgent < public static void premain(String agentArguments, Instrumentation instrumentation)< System.out.println('Simple Agent'); SimpleClassTransformer transformer = new SimpleClassTransformer(); instrumentation.addTransformer(transformer); >>
Note that the very purpose of having agent is to provide instrumentation capabilities to the application – i.e. the capability to re-define the signature of the class files during run-time. The method premain() is passed with Instrumentation object that serves its very purpose. Using Instrumentation object it is possible to adding transformer objects. Transformer object does the real job of transforming (or re-defining) the content of class files at run-time. The above code defines a transformer object, whose declaration is given below,
package net.javabeat.articles.javaagent.simple; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; public class SimpleClassTransformer implements ClassFileTransformer < public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException < System.out.println(className); return classfileBuffer; >>
Note that, a transformer object must implement the ClassFileTransformer interface and the abstract method transform needs to be overridden. This method will be called for every class that is loaded as part of the application. Note that the content of the class can be represented as byte array and this method can re-define the class content that returnss a different byte array (as denoted by the method signature). For simplicity, this method returns the original class byte array that is passed to it.
Manifest-Version: 1.0 Premain-Class: net.javabeat.articles.javaagent.simple.SimpleAgent
Given above is the content of the manifest file for the agent. Note that for testing the agent, we need a sample application file which is given below. To illustrate the behavior of class loading, the application creates instances for Test classes.
package net.javabeat.articles.javaagent.simple; public class SimpleMain < public static void main(String[] args) < Test1 one = new Test1(); Test2 two = new Test2(); System.out.println('Simple Main ' + one + two); >> class Test1<> class Test2<>
Running the above application along with the agent will produce the following output.
Simple Agent net/javabeat/articles/javaagent/simple/SimpleMain net/javabeat/articles/javaagent/simple/Test1 net/javabeat/articles/javaagent/simple/Test2 Simple Main net.javabeat.articles.javaagent.simple. Test1@61de33net.javabeat.articles.javaagent.simple.Test2@14318bb
Agents for collecting statistical data
In the last section, we have seen how to make use of Instrumentation object. We also saw how to manipulate Instrumentation object by adding Transformer objects. In this final section, we will see the real capability of agents for providing some statistical information about the methods being loaded for a particular class. For the purpose of illustration, let us expand the scope of the sample by provide a business class having empty business methods. Let us also assume that because the methods are for external usage, all the methods have to be public.
package net.javabeat.articles.javaagent.statistics; public class MyBusinessClass < public void bizMethod1()< System.out.println('Biz Method 1'); >@SuppressWarnings('unused') private void bizMethod2() < System.out.println('Biz Method 2'); >>
The above code has the definition of the business class. Deliberately, the modifier for the method bizMethod2() is given as private. The agent class is given below. Note that, as illustrated in the last section, this class makes use of Instrumentation object to add transformer objects to it.
package net.javabeat.articles.javaagent.statistics; import java.lang.instrument.Instrumentation; public class StatisticsAgent < public static void premain(String agentArguments, Instrumentation instrumentation)< StatisticsClassTransformer transformer = new StatisticsClassTransformer(); instrumentation.addTransformer(transformer); >>
The flavor of the Transformer class that has the functionality of providing statistical information is given below. At this point, note that by the time when the agent is invoked, the class is not loaded. It is only during the class loading time, the agent is invoked. Hence reflection related APIs cannot be used at this point. For querying the method information for a particular class, the implementation makes use of ASM (byte code analysis library).
package net.javabeat.articles.javaagent.statistics; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.security.ProtectionDomain; import java.util.List; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; public class StatisticsClassTransformer implements ClassFileTransformer < public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException < System.out.println(); System.out.println('Processing class ' + className); String normalizedClassName = className.replaceAll('/', '.'); ClassReader classReader = null; try < classReader = new ClassReader(normalizedClassName); >catch (IOException e1) < e1.printStackTrace(); >ClassNode classNode = new ClassNode(); classReader.accept(classNode, ClassReader.SKIP_DEBUG); @SuppressWarnings('unchecked') List allMethods = classNode.methods; for (MethodNode methodNode : allMethods) < System.out.println(methodNode.name); >return classfileBuffer; > private static void processBizMethods(Class classObject) < if (MyBusinessClass.class.equals(classObject))< Method[] allMethods = classObject.getDeclaredMethods(); for (Method aMethod : allMethods)< System.out.println(aMethod.getName()); int modifiers = aMethod.getModifiers(); if (Modifier.isPrivate(modifiers))< System.out.println('Method ' + aMethod.getName() + ' is private'); >> > > public static void main(String[] args) < processBizMethods(MyBusinessClass.class); >>
The content of the manifest file for the above agent is given below.
Manifest-Version: 1.0 Premain-Class: net.javabeat.articles.javaagent.statistics.StatisticsAgent
The content of the application that triggers the agent class is given below.
package net.javabeat.articles.javaagent.statistics; public class StatisticsMain < public static void main(String[] args) < MyBusinessClass object = new MyBusinessClass(); System.out.println('Biz Object ' + object); >>
The output of the application is given below,
Processing class net/javabeat/articles/javaagent/statistics/StatisticsMain main Processing class net/javabeat/articles/javaagent/statistics/MyBusinessClass bizMethod1 bizMethod2 Biz Object net.javabeat.articles.javaagent.statistics.MyBusinessClass@a59698
Conclusion
Download Source Code: [download >
This article provides introductory concepts about Java agents which are introduced from 5.0. It provides a good starter about the usage of agents by providing lot of examples. Hope the readers will be benefited after reading this article.
About Krishna Srinivasan
He is Founder and Chief Editor of JavaBeat. He has more than 8+ years of experience on developing Web applications. He writes about Spring, DOJO, JSF, Hibernate and many other emerging technologies in this blog.