Previous
Background
The only requirement for a class used to register a Java Agent is a premain method:
package schuchert.agent ;
import java.lang.instrument.ClassFileTransformer ;
import java.lang.instrument.Instrumentation ;
public class ConfigurableClassFileTransformerRedirector {
public static void premain ( String agentArguments , Instrumentation instrumentation ) {
}
}
This is a complete, minimal example. If this class is packaged in a jar file with a correct manifest, when the VM starts up, the premain method gets called and has a chance to register an instance of a ClassFileTransformer with the instrumentation instance.
registrar
Here’s a simple class that will report the unqualified class name and its package:
package schuchert.agent ;
import java.lang.instrument.ClassFileTransformer ;
import java.lang.instrument.IllegalClassFormatException ;
import java.security.ProtectionDomain ;
public class ClassAndPackageNamePrintingClassFileTransformer implements ClassFileTransformer {
public byte [] transform ( ClassLoader loader , String fullyQualifiedClassName , Class <?> classBeingRedefined ,
ProtectionDomain protectionDomain , byte [] classofileBuffer ) throws IllegalClassFormatException {
String className = fullyQualifiedClassName . replaceAll ( ".*/" , "" );
String pacakge = fullyQualifiedClassName . replaceAll ( "/[a-zA-Z$0-9_]*$" , "" );
System . out . printf ( "Class: %s in: %s\n" , className , pacakge );
return null ;
}
}
To get this ClassFileTransformer registered, we’d change pre-main to the following:
public static void premain ( String agentArguments , Instrumentation instrumentation ) {
instrumentation . addTransformer ( new ClassAndPackageNamePrintingClassFileTransformer ());
}
Below is the source for the registrar. Here are a couple of notes:
Many of the output strings are exposed as package-level constants to make testing easier
There’s a static filed, ERROR_OUT, exposed for testability as well
This class records the each class it instantiates in registeredTransformers, again for testability
This class allows a : separated list of classes. E.g. rather than using multiple -javaagent: lines or passing parameters into the premain(…), I’ve choses to support a : separated list.
If you want to see the tests that verify this class’ functionality, look further below. Note that the test class uses JUnit 4.4 and JMock 2.4.
package schuchert.agent ;
import java.io.PrintStream ;
import java.lang.instrument.ClassFileTransformer ;
import java.lang.instrument.Instrumentation ;
import java.util.Iterator ;
import java.util.LinkedList ;
import java.util.List ;
public class ConfigurableClassFileTransformerRegistrar {
private static List < ClassFileTransformer > registeredTransformers = new LinkedList < ClassFileTransformer >();
public static final String DEST_CLASS_PROPERTY_NAME = "schuchert.ClassFileTransformer" ;
static final String INSTANTIATION_ERROR = "Unable to instantiate: %s\n" ;
static final String CHECK_SYSTEM_PROPERTY_MESSAGE = "Please check setting for system property: %s\n" ;
static final String EXAMPLE_MESSAGE = "e.g. -D%s=%s\n" ;
static PrintStream ERROR_OUT = System . err ;
public static void premain ( String agentArguments , Instrumentation instrumentation ) {
for ( String className : getClassNames ()) {
addOneClassFileTransformer ( instrumentation , className );
}
}
public static Iterator < ClassFileTransformer > iterator () {
return registeredTransformers . iterator ();
}
private static void addOneClassFileTransformer ( Instrumentation instrumentation , String className ) {
ClassFileTransformer classFileTransformer = createTargetTransformer ( className );
if ( classFileTransformer != null ) {
instrumentation . addTransformer ( classFileTransformer );
registeredTransformers . add ( classFileTransformer );
}
}
private static ClassFileTransformer createTargetTransformer ( String className ) {
ClassFileTransformer targetTransformer = null ;
if ( className != null ) {
try {
Class <?> targetTransformerClass = Class . forName ( className );
targetTransformer = ( ClassFileTransformer ) targetTransformerClass . newInstance ();
} catch ( Exception e ) {
reportException ( e , className );
}
}
return targetTransformer ;
}
private static String [] getClassNames () {
String classNameList = System . getProperty ( DEST_CLASS_PROPERTY_NAME );
if ( classNameListMissingOrEmpty ( classNameList )) {
reportUsage ();
return new String [] {};
}
String [] classNames = classNameList . split ( ":" );
for ( int i = 0 ; i < classNames . length ; ++ i ) {
classNames [ i ] = classNames [ i ]. trim ();
}
return classNames ;
}
private static boolean classNameListMissingOrEmpty ( String classNameList ) {
return classNameList == null || classNameList . trim (). length () == 0 ;
}
private static void reportException ( Exception e , String className ) {
e . printStackTrace ( ERROR_OUT );
ERROR_OUT . printf ( INSTANTIATION_ERROR , className );
reportUsage ();
}
private static void reportUsage () {
ERROR_OUT . printf ( CHECK_SYSTEM_PROPERTY_MESSAGE , DEST_CLASS_PROPERTY_NAME );
ERROR_OUT . printf ( EXAMPLE_MESSAGE , DEST_CLASS_PROPERTY_NAME , NullClassFileTransformer . class . getName ());
}
}
package schuchert.agent ;
import java.io.PrintStream ;
import java.lang.instrument.ClassFileTransformer ;
import java.lang.instrument.Instrumentation ;
import java.util.Properties ;
import junit.framework.Assert ;
import org.jmock.Expectations ;
import org.jmock.Mockery ;
import org.jmock.integration.junit4.JMock ;
import org.junit.Before ;
import org.junit.Test ;
import org.junit.runner.RunWith ;
import util.MockeryUtil ;
@RunWith ( JMock . class )
public class ConfigurableClassFileTransformerRegistrarTest {
Mockery context = MockeryUtil . createMockeryForConcreteClasses ();
Instrumentation insrumentationSpy ;
PrintStream errorOutSpy ;
@Before
public void createInsrumentationSpy () {
insrumentationSpy = context . mock ( Instrumentation . class );
}
@Before
public void createAndRegisterErrorOutSpy () {
errorOutSpy = context . mock ( PrintStream . class );
ConfigurableClassFileTransformerRegistrar . ERROR_OUT = errorOutSpy ;
}
@Before
public void clearSystemProperty () {
Properties systemProperties = System . getProperties ();
systemProperties . remove ( ConfigurableClassFileTransformerRegistrar . DEST_CLASS_PROPERTY_NAME );
System . setProperties ( systemProperties );
Assert . assertNull ( System . getProperty ( ConfigurableClassFileTransformerRegistrar . DEST_CLASS_PROPERTY_NAME ));
}
@Test
public void errorReportedAndNoClassRegisteredWhenNoSystemPropertyDefined () {
expectMissingSystemPropertyMessage ();
ConfigurableClassFileTransformerRegistrar . premain ( "" , insrumentationSpy );
}
@Test
public void systemPropertyDefinedButNullAlwaysPassesBecuaseNotPossibleToCreaetNullSystemProperty () {
}
@Test
public void missingSystemPropertyReportedAndNoClassRegisteredWhenSystemPropertyEmptyString () {
expectMissingSystemPropertyMessage ();
setSystemProperty ( "" );
ConfigurableClassFileTransformerRegistrar . premain ( "" , insrumentationSpy );
}
@Test
public void missingSystemPropertyReportedAndNoClassRegisteredWhenSystemPropertyContainsNothingButSpaces () {
expectMissingSystemPropertyMessage ();
setSystemProperty ( " " );
ConfigurableClassFileTransformerRegistrar . premain ( "" , insrumentationSpy );
}
@Test
public void noErrorsWhenSystemPropertySetToSingleClassThatExistsInClassPath () {
expectRegistryOfNullClassFileTransformer ();
setSystemProperty ( NullClassFileTransformer . class . getName ());
ConfigurableClassFileTransformerRegistrar . premain ( "" , insrumentationSpy );
}
@Test
public void noErrorsWhenSystemPropertySetToSingleClassWithSpacesOnEndsThatExistsInClassPath () {
expectRegistryOfNullClassFileTransformer ();
setSystemProperty ( " " + NullClassFileTransformer . class . getName () + " " );
ConfigurableClassFileTransformerRegistrar . premain ( "" , insrumentationSpy );
}
@Test
public void noErrorsWhenSystemPropertySetToMultipleClassesAllOfWhichAreInClassPath () {
expectRegistryOfNullClassFileTransformer ();
expectRegistryOf ( ClassAndPackageNamePrintingClassFileTransformer . class );
setSystemProperty ( NullClassFileTransformer . class . getName () + ":"
+ ClassAndPackageNamePrintingClassFileTransformer . class . getName ());
ConfigurableClassFileTransformerRegistrar . premain ( "" , insrumentationSpy );
}
@Test
public void noErrorsWhenSystemPropertySetToMultipleClassesWithSpacesAtEndsOfNames () {
expectRegistryOfNullClassFileTransformer ();
expectRegistryOf ( ClassAndPackageNamePrintingClassFileTransformer . class );
setSystemProperty ( " " + NullClassFileTransformer . class . getName () + " : "
+ ClassAndPackageNamePrintingClassFileTransformer . class . getName () + " " );
ConfigurableClassFileTransformerRegistrar . premain ( "" , insrumentationSpy );
}
@Test
public void noClassRegisteredWhenSingleMissingClassInSystemProperty () {
expectReportOfClassNotFound ();
setSystemProperty ( "MissingClassThatShouldNotExistInClassPathEver" );
ConfigurableClassFileTransformerRegistrar . premain ( "" , insrumentationSpy );
}
@Test
public void nullTransformerRegisteredWhenSingleBadClassNameInSystemProperty () {
expectReportOfClassNotFound ();
setSystemProperty ( "InvalidClassName^^^^" );
ConfigurableClassFileTransformerRegistrar . premain ( "" , insrumentationSpy );
}
@Test
public void nullTransformerRegisteredForEachBadClassWhenBothGoodAndBadClassNamesInSystemProperty () {
expectRegistryOfNullClassFileTransformer ();
expectReportOfClassNotFound ();
expectRegistryOf ( ClassAndPackageNamePrintingClassFileTransformer . class );
setSystemProperty ( NullClassFileTransformer . class . getName () + ":InvalidClassname^^^:"
+ ClassAndPackageNamePrintingClassFileTransformer . class . getName ());
ConfigurableClassFileTransformerRegistrar . premain ( "" , insrumentationSpy );
}
@Test
public void classNotImplementingClassFileFormaterErrorReported () {
expectClassCastException ();
expectReportOfMissingClass ();
expectMissingSystemPropertyMessage ();
setSystemProperty ( getClass (). getName ());
ConfigurableClassFileTransformerRegistrar . premain ( "" , insrumentationSpy );
}
private void setSystemProperty ( String value ) {
System . setProperty ( ConfigurableClassFileTransformerRegistrar . DEST_CLASS_PROPERTY_NAME , value );
}
private void expectMissingSystemPropertyMessage () {
context . checking ( new Expectations () {
{
one ( errorOutSpy ). printf ( with ( equal ( ConfigurableClassFileTransformerRegistrar . CHECK_SYSTEM_PROPERTY_MESSAGE )),
with ( any ( String . class )));
one ( errorOutSpy ). printf ( with ( equal ( ConfigurableClassFileTransformerRegistrar . EXAMPLE_MESSAGE )),
with ( any ( Object . class )));
}
});
}
private void expectRegistryOfNullClassFileTransformer () {
expectRegistryOf ( NullClassFileTransformer . class );
}
private void expectRegistryOf ( final Class <?> clazz ) {
context . checking ( new Expectations () {
{
one ( insrumentationSpy ). addTransformer (( ClassFileTransformer ) with ( any ( clazz )));
}
});
}
private void expectReportOfMissingClass () {
context . checking ( new Expectations () {
{
one ( errorOutSpy ). printf ( with ( equal ( ConfigurableClassFileTransformerRegistrar . INSTANTIATION_ERROR )),
with ( any ( String . class )));
}
});
}
private void expectClassNotFoundExceptionReported () {
context . checking ( new Expectations () {
{
one ( errorOutSpy ). println ( with ( any ( ClassNotFoundException . class )));
ignoring ( errorOutSpy ). println ( with ( any ( String . class )));
}
});
}
private void expectReportOfClassNotFound () {
expectClassNotFoundExceptionReported ();
expectReportOfMissingClass ();
expectMissingSystemPropertyMessage ();
}
private void expectClassCastException () {
context . checking ( new Expectations () {
{
one ( errorOutSpy ). println ( with ( any ( ClassCastException . class )));
ignoring ( errorOutSpy ). println ( with ( any ( String . class )));
}
});
}
}
Previous
Comments