Skip to main content

skip to main content

developerWorks  >  Java technology | Open source  >

Classworking toolkit: Annotations with ASM

Automate run time classfile modification

developerWorks
Document options

Document options requiring JavaScript are not displayed

Sample code


Rate this page

Help us improve this content


Level: Intermediate

Dennis Sosnoski (dms@sosnoski.com), President, Sosnoski Software Solutions, Inc.

07 Jun 2005

Are you tired of building and maintaining toString() methods for all your data classes? In this edition of Classworking toolkit, consultant Dennis Sosnoski shows how you can automate the process using J2SE 5.0 annotations and the ASM bytecode manipulation framework. He takes advantage of the new J2SE 5.0 instrumentation API to invoke ASM as classes are loaded into the JVM, providing on-the-fly class modification at run time.

With J2SE 5.0, Sun has added a number of new features to the Java™ platform. One of the most important new features is support for annotations. Annotations promise to be useful for associating many types of metadata with Java code and are already being used extensively to replace custom configuration files in new and updated JSRs for extensions to the Java platform. In this column, I'll show how you can use the ASM bytecode manipulation framework in combination with another new J2SE 5.0 feature -- the instrumentation package -- to transform classes as they're being loaded into the JVM as directed by annotations.

Annotation basics

Many articles have already been published discussing J2SE 5.0 annotations (see Resources for some), so I'll just include a brief summary here. Annotations are a form of metadata for Java code. They're similar in function to the XDoclet-style metadata that's become increasingly popular for working with complex framework configurations, but the implementation has much more in common with C# attributes than with XDoclet.

The Java implementation of this language feature uses an interface-like structure with some special extensions to the Java language syntax. I find it clearer to ignore this interface-like structure for most purposes and instead think of annotations as a hashmap of name-value pairs. Each annotation type defines a fixed set of names associated with that annotation. Each name may be given a default value but otherwise need to be defined for each use of the annotation. Annotations can be specified to apply to a particular type of Java component (such as a class, field, method, and so on) and can even be applied to other annotations. (In fact, the way you restrict the components an annotation can be used for is by using a special predefined annotation on the definition of the annotation to be restricted.)

Ask the expert: Dennis Sosnoski on JVM and bytecode issues
For comments or questions about the material covered in this article series, as well as anything else that pertains to Java bytecode, the Java binary class format, or general JVM issues, visit the JVM and Bytecode discussion forum, moderated by Dennis Sosnoski.

Unlike regular interfaces, annotations must use the keyword @interface in their definitions. Also unlike regular interfaces, annotations can only define "methods" that take no parameters and return only simple values (primitive types, String, Class, enum types, annotations, and arrays of any of these types). These "methods" are the names for the values associated with the annotation.

Annotations are used as modifiers on declarations, just like public, final, and other keyword modifiers defined by the Java language prior to J2SE 5.0. The use of an annotation is indicated by the @ symbol followed by the annotation name. If values are to be supplied for the annotation, these are given as name-value pairs in parentheses following the annotation name.

Listing 1 shows a sample annotation declaration followed by the definition of a class using that annotation on some methods. This LogMe annotation is intended to flag methods that should be included in the logging for an application. I've given the annotation two values: one representing the level of logging in which this call is to be included and the other the name to be used for the method call (defaulting to an empty string, on the assumption that the code that handles this annotation will substitute the actual method name when no name is supplied). I then use this annotation on a pair of methods in the StringArray class, for the merge() method just using the default values, and for the indexOf() method supplying explicit values.


Listing 1. Logging annotation definition and usage example

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

/**
 * Annotation for method to be included in logging.
 */
@Target({ElementType.METHOD})
public @interface LogMe {
    int level() default 0;
    String name() default "";
}

public class StringArray
{
    private final String[] m_list;
    
    public StringArray(String[] list) {
        ...
    }
    
    public StringArray(StringArray base, String[] adds) {
        ...
    }
    
    @LogMe private String[] merge(String[] list1, String[]list2) {
        ...
    }
    
    public String get(int index) {
        return m_list[index];
    }
    
    @LogMe(level=1, name="lookup") public int indexOf(String value) {
        ...
    }
    
    public int size() {
        return m_list.length;
    }
}

The next section introduces a different (and I think more interesting) application.



Back to top


Building a toString()

The Java platform provides a convenient hook for generating a text description of an object in the form of the toString() method. The ultimate base class java.lang.Object provides a default implementation of this method but encourages overriding the default implementation to supply a more useful description. Many developers make a habit of providing their own implementations, at least for classes that are largely data representations. I'll confess up front that I'm not one of them -- I've often found toString() useful, but generally I don't bother overriding the default. To be useful, a toString() implementation needs to be kept up to date as fields are added and removed from the class, and I find this step too much trouble to be worthwhile in general.

Combining annotations with class file modification can provide a way out of this dilemma. The problem I have with maintaining a toString() method is because of the code being separate from the field declarations in the class, meaning I have another thing I need to remember to change anytime I add or remove a field. By using annotations on the field declarations, I can easily indicate which fields I want included in the toString() method, while leaving the actual implementation of the method to a classworking tool. That way everything is in one place (the field declaration), and I get my useful description out of toString() without needing to maintain the code.

Sampling the source

Before launching into implementing an annotation for toString() method construction, I'll give a sample of what I'd like to accomplish. Listing 2 shows a simple data holder class with a toString() method included in the source code:


Listing 2. Data class with the toString() method

public class Address
{
    private String m_street;
    private String m_city;
    private String m_state;
    private String m_zip;
    
    public Address() {}

    public Address(String street, String city, String state, String zip) {
        m_street = street;
        m_city = city;
        m_state = state;
        m_zip = zip;
    }
    public String getCity() {
        return m_city;
    }
    public void setCity(String city) {
        m_city = city;
    }
    ...
    public String toString() {
        StringBuffer buff = new StringBuffer();
        buff.append("Address: street=");
        buff.append(m_street);
        buff.append(", city=");
        buff.append(m_city);
        buff.append(", state=");
        buff.append(m_state);
        buff.append(", zip=");
        buff.append(m_zip);
        return buff.toString();
    }
}

For the Listing 2 sample, I've chosen to include all the fields in the toString() output in the same order they're declared in the class and to preface each field value with a "name=" text to identify it in the output. For this case, the text is generated directly from the field name by just stripping off the leading "m_" prefix I use to identify member fields. In other cases, I may want to only include certain fields in the output, change the order, change the identifier text used for a value, or even skip the identifier text completely. The annotation format should be flexible enough to express all these possibilities.

Defining the annotation

You can define annotations for toString() generation in many different ways. To really make it useful, I'd want to minimize the number of annotations required, perhaps by using a class annotation to flag the classes where I want the method generated in combination with individual field annotations to override the default handling of fields. That's not too difficult to do, but the implementation code becomes fairly complex. For this article, I'm going to keep it simple and just use an annotation on the individual fields to be included in the description of an instance.

The factors I want to control are which fields are included, whether a field value has leading text, whether that text is based on the field name, and the order of fields in the output. Listing 3 gives a basic annotation for this purpose:


Listing 3. Annotation for toString() generation

package com.sosnoski.asm;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.FIELD})
public @interface ToString {
    int order() default 0;
    String text() default "";
}

The Listing 3 annotation just defines a pair of named values, giving the order and lead text to be used for a field. I've restricted the use of this annotation to field declarations with the @Target line. I've also defined defaults for each of the values. These defaults don't apply to the generated annotation information that goes into the binary class representation (they only apply when annotations are accessed at run time as a pseudo-interface, which I won't be doing), so I don't actually care what values are used here. By defining defaults, I'm just making the values optional so that I don't have to specify them each time I use the annotation.

One factor to keep in mind when working with annotations is that the named values must always be compile-time constants and can never be null. This rule applies to both the default values, if given, and to values set by the user. I gather this decision was made on the basis of consistency with the earlier Java language definition, though I find it strange that a specification that makes such major modifications to the Java language would limit itself to consistency in this one area.



Back to top


Implementing the generation

Now that I have laid the groundwork, it's time to look at implementing the classworking transformation that will add toString() methods to annotated classes as they're being loaded. This implementation involves three separate pieces of code: intercepting the classloading, accessing the annotation information, and the actual transformation.

Intercepting with instrumentation

J2SE 5.0 adds many features to the Java platform. I'm not personally convinced that all these additions are really improvements. However, two little-noticed new features that are truly useful for classworking are the java.lang.instrument package and JVM interface, which let you (among other things) specify class transformation agents to be used when executing a program.

To use a transformation agent, you need to specify the agent class when you start the JVM. When using the java command to launch the JVM, you can specify agents using command line parameters of the form -javaagent:jarpath[=options], where "jarpath" is the path to the JAR file containing the agent class, and "options" is a parameter string for the agent. The agent JAR file uses a special manifest attribute to specify the actual agent class, which must define a method public static void premain(String options, Instrumentation inst). This agent premain() method will be called before the application's main() method and is able to register an actual transformer with the passed-in java.lang.instrument.Instrumentation class instance.

The transformer class must implement the java.lang.instrument.ClassFileTransformer interface, which defines a single transform() method. When a transformer instance is registered with the Instrumentation class instance, that transformer instance will be called for each class being created in the JVM. The transformer gets access to the binary class representation and can modify the class representation before it is loaded by the JVM.

Listing 4 gives the agent and transformer class (both the same class in this case, though they don't need to be) implementation for processing the annotations. The transform() implementation uses ASM to scan the supplied binary class representation and look for the appropriate annotations, collecting information about the annotated fields of the class. If annotated fields are found, the class is modified to include a generated toString() method and the modified binary representation is returned. Otherwise the transform() method just returns null to indicate that no modifications are necessary.


Listing 4. Agent and transformer class

package com.sosnoski.asm;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

public class ToStringAgent implements ClassFileTransformer
{
    // transformer interface implementation
    public byte[] transform(ClassLoader loader, String cname, Class class,
        ProtectionDomain domain, byte[] bytes)
        throws IllegalClassFormatException {
        System.out.println("Processing class " + cname);
        try {
            
            // scan class binary format to find fields for toString() method
            ClassReader creader = new ClassReader(bytes);
            FieldCollector visitor = new FieldCollector();
            creader.accept(visitor, true);
            FieldInfo[] fields = visitor.getFields();
            if (fields.length > 0) {
                
                // annotated fields present, generate the toString() method
                System.out.println("Modifying " + cname);
                ClassWriter writer = new ClassWriter(false);
                ToStringGenerator gen = new ToStringGenerator(writer,
                        cname.replace('.', '/'), fields);
                creader.accept(gen, false);
                return writer.toByteArray();
                
            }
        } catch (IllegalStateException e) {
            throw new IllegalClassFormatException("Error: " + e.getMessage() +
                " on class " + cname);
        }
        return null;
    }
    
    // Required method for instrumentation agent.
    public static void premain(String arglist, Instrumentation inst) {
        inst.addTransformer(new ToStringAgent());
    }
}

The instrumentation features of J2SE 5.0 go beyond what I've shown here, including the ability to access all the classes that have been loaded into the JVM and even to redefine existing classes (if supported by the JVM). For this column, I'll skip these other features and just move on to the ASM code used to process the annotations and modify a class.

Accumulating the metadata

ASM 2.0 makes processing annotations easy. As you learned last month, ASM uses a visitor approach for reporting all components of class data. J2SE 5.0 annotations are reported using the org.objectweb.asm.AnnotationVisitor interface. This interface defines several methods, of which I'm only going to use two: visitAnnotation() is the method called when processing an annotation, and visit() is the method called when processing a particular name-value pair for an annotation. I also need the actual field information, which is reported using the visitField() method of the basic org.objectweb.asm.ClassVisitor interface.

Implementing all the methods of the two interfaces of interest would be tedious, but fortunately ASM provides a convenient org.objectweb.asm.commons.EmptyVisitor class as a base for writing your own visitors. EmptyVisitor just provides an empty implementation of all the various types of visitors, allowing you to subclass and override only the visitor methods of interest. Listing 5 gives my implementation of the FieldCollector class for processing ToString annotations, extending the EmptyVisitor class. It also includes the FieldInfo class used to hold collected field information.


Listing 5. Annotation processing class

package com.sosnoski.asm;

import java.util.ArrayList;
import java.util.Arrays;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.EmptyVisitor;

/**
 * Visitor implementation to collect field annotation information from class.
 */
public class FieldCollector extends EmptyVisitor
{
    private boolean m_isIncluded;
    private int m_fieldAccess;
    private String m_fieldName;
    private Type m_fieldType;
    private int m_fieldOrder;
    private String m_fieldText;
    private ArrayList m_fields = new ArrayList();
    
    // finish field handling, once we're past it
    private void finishField() {
        if (m_isIncluded) {
            m_fields.add(new FieldInfo(m_fieldName, m_fieldType,
                m_fieldOrder, m_fieldText));
        }
        m_isIncluded = false;
    }
    
    // return array of included field information
    public FieldInfo[] getFields() {
        finishField();
        FieldInfo[] infos =
            (FieldInfo[])m_fields.toArray(new FieldInfo[m_fields.size()]);
        Arrays.sort(infos);
        return infos;
    }
    
    // process field found in class
    public FieldVisitor visitField(int access, String name, String desc,
        String sig, Object init) {
        
        // finish processing of last field
        finishField();
        
        // save information for this field
        m_fieldAccess = access;
        m_fieldName = name;
        m_fieldType = Type.getReturnType(desc);
        m_fieldOrder = Integer.MAX_VALUE;
        
        // default text is empty if non-String object, otherwise from field name
        if (m_fieldType.getSort() == Type.OBJECT &&
            !m_fieldType.getClassName().equals("java.lang.String")) {
            m_fieldText = "";
        } else {
            String text = name;
            if (text.startsWith("m_") && text.length() > 2) {
                text = Character.toLowerCase(text.charAt(2)) +
                    text.substring(3);
            }
            m_fieldText = text;
        }
        return super.visitField(access, name, desc, sig, init);
    }
    
    // process annotation found in class
    public AnnotationVisitor visitAnnotation(String sig, boolean visible) {
        
        // flag field to be included in representation
        if (sig.equals("Lcom/sosnoski/asm/ToString;")) {
            if ((m_fieldAccess & Opcodes.ACC_STATIC) == 0) {
                m_isIncluded = true;
            } else {
                throw new IllegalStateException("ToString " +
                    "annotation is not supported for static field +" +
                    " m_fieldName");
            }
        }
        return super.visitAnnotation(sig, visible);
    }
    
    // process annotation name-value pair found in class
    public void visit(String name, Object value) {
        
        // ignore anything except the pair defined for toString() use
        if ("order".equals(name)) {
            m_fieldOrder = ((Integer)value).intValue();
        } else if ("text".equals(name)) {
            m_fieldText = value.toString();
        }
    }
}

package com.sosnoski.asm;

import org.objectweb.asm.Type;

/**
 * Information for field value to be included in string representation.
 */
public class FieldInfo implements Comparable
{
    private final String m_field;
    private final Type m_type;
    private final int m_order;
    private final String m_text;
    
    public FieldInfo(String field, Type type, int order,
        String text) {
        m_field = field;
        m_type = type;
        m_order = order;
        m_text = text;
    }
    public String getField() {
        return m_field;
    }
    public Type getType() {
        return m_type;
    }
    public int getOrder() {
        return m_order;
    }
    public String getText() {
        return m_text;
    }
    
    /* (non-Javadoc)
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    public int compareTo(Object comp) {
        if (comp instanceof FieldInfo) {
            return m_order - ((FieldInfo)comp).m_order;
        } else {
            throw new IllegalArgumentException("Wrong type for comparison");
        }
    }
}

The Listing 5 code saves the field information at the time a field is visited, because this information will be needed later if the field has an annotation present. When an annotation is visited, the code checks whether it is a ToString annotation, and if so sets a flag that the current field should be included in the list to be used for generating the toString() method. When an annotation name-value pair is visited, the code checks for the two names defined by the ToString annotation and saves the value for each name when found. The real default values for these names (as opposed to the defaults used in the annotation definition) are set up in the field visitor method, so any value specified by the user will overwrite these defaults.

ASM visits the field first, followed by the annotation and annotation values. There's no particular method that gets called when you're done processing the annotations for a field, so I just have a finishField() method that I call when processing a new field and when the completed list of fields is requested. The getFields() method provides this completed list of fields to the caller, ordered as determined by the annotation values.

Transforming the class

Listing 6 shows the final portion of the implementation code, which actually adds the toString() method to a class. This code is similar to the code in last month's column for constructing a class using ASM, but it needs to be structured differently to modify an existing class. Here the visitor approach used by ASM adds a little complexity -- to modify an existing class, you need to visit all the current class content and pass it through to a class writer, filtering out portions you want to remove and adding your new content directly to the writer. org.objectweb.asm.ClassAdapter is a convenient base class for this purpose. It implements the pass-through handling for a supplied class writer instance, letting you override only the methods where you need special handling.


Listing 6. Adding the toString() method

package com.sosnoski.asm;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

/**
 * Visitor to add <code>toString</code> method to a class.
 */
public class ToStringGenerator extends ClassAdapter
{
    private final ClassWriter m_writer;
    private final String m_internalName;
    private final FieldInfo[] m_fields;
    
    public ToStringGenerator(ClassWriter cw, String iname, FieldInfo[] props) {
        super(cw);
        m_writer = cw;
        m_internalName = iname;
        m_fields = props;
    }
    
    // called at end of class
    public void visitEnd() {
        
        // set up to build the toString() method
        MethodVisitor mv = m_writer.visitMethod(Opcodes.ACC_PUBLIC,
            "toString", "()Ljava/lang/String;", null, null);
        mv.visitCode();
        
        // create and initialize StringBuffer instance
        mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuffer");
        mv.visitInsn(Opcodes.DUP);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuffer",
            "<init>", "()V");
        
        // start text with class name
        String name = m_internalName;
        int split = name.lastIndexOf('/');
        if (split >= 0) {
            name = name.substring(split+1);
        }
        mv.visitLdcInsn(name + ":");
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuffer",
            "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
        
        // loop through all field values to be included
        boolean newline = false;
        for (int i = 0; i < m_fields.length; i++) {
            
            // check type of field (objects other than Strings need conversion)
            FieldInfo prop = m_fields[i];
            Type type = prop.getType();
            boolean isobj = type.getSort() == Type.OBJECT &&
                !type.getClassName().equals("java.lang.String");
            
            // format lead text, with newline for object or after object
            String lead = (isobj || newline) ? "\n " : " ";
            if (prop.getText().length() > 0) {
                lead += prop.getText() + "=";
            }
            mv.visitLdcInsn(lead);
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
                "java/lang/StringBuffer", "append",
                "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
            
            // load the actual field value and append
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitFieldInsn(Opcodes.GETFIELD, m_internalName,
                prop.getField(), type.getDescriptor());
            if (isobj) {
                
                // convert objects by calling toString() method
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
                    type.getInternalName(), "toString",
                    "()Ljava/lang/String;");
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
                    "java/lang/StringBuffer", "append",
                    "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
                
            } else {
                
                // append other types directly to StringBuffer
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
                    "java/lang/StringBuffer", "append", "(" +
                    type.getDescriptor() + ")Ljava/lang/StringBuffer;");
                
            }
            newline = isobj;
        }
        
        // finish the method by returning accumulated text
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuffer",
            "toString", "()Ljava/lang/String;");
        mv.visitInsn(Opcodes.ARETURN);
        mv.visitMaxs(3, 1);
        mv.visitEnd();
        super.visitEnd();
    }
}

In Listing 6, the only method I need to override is the visitEnd() method. This method gets called after all the existing class information has been visited, so it's a convenient place to implement the addition of new content. I've used the visitEnd() method to add the toString() method to the class being processed. In the code generation, I've added a few features for nicely formatting the toString() output, but the basic principle is simple -- just loop through the supplied array of fields, generating the code to append first the lead text and then the actual field value to a StringBuffer instance.

Because the current code will only work with J2SE 5.0 (due to the use of the instrumentation methods to intercept classloading), I could have used the new StringBuilder class as a more efficient equivalent to StringBuffer. I chose to go with the older alternative because of some follow-up work I'm going to do with this code next column, but it's worth keeping StringBuilder in mind for your own J2SE 5.0-specific code.

ToString in action

Listing 7 shows some test classes for the ToString annotation. I've used a mixture of different styles for the actual annotations, specifying name-value pairs in some cases and just using the annotation by itself in other cases. The Run class just creates an instance of the Customer class with some sample data and prints the results of a toString() method call.


Listing 7. Test classes for ToString

package com.sosnoski.dwct;

import com.sosnoski.asm.ToString;

public class Customer
{
    @ToString(order=1, text="#") private long m_number;
    @ToString() private String m_homePhone;
    @ToString() private String m_dayPhone;
    @ToString(order=2) private Name m_name;
    @ToString(order=3) private Address m_address;
    
    public Customer() {}
    public Customer(long number, Name name, Address address, String homeph,
        String dayph) {
        m_number = number;
        m_name = name;
        m_address = address;
        m_homePhone = homeph;
        m_dayPhone = dayph;
    }
    ...
}
...
public class Address
{
    @ToString private String m_street;
    @ToString private String m_city;
    @ToString private String m_state;
    @ToString private String m_zip;
    
    public Address() {}
    public Address(String street, String city, String state, String zip) {
        m_street = street;
        m_city = city;
        m_state = state;
        m_zip = zip;
    }
    public String getCity() {
        return m_city;
    }
    public void setCity(String city) {
        m_city = city;
    }
    ...
}
...
public class Name
{
    @ToString(order=1, text="") private String m_first;
    @ToString(order=2, text="") private String m_middle;
    @ToString(order=3, text="") private String m_last;
    
    public Name() {}
    public Name(String first, String middle, String last) {
        m_first = first;
        m_middle = middle;
        m_last = last;
    }
    public String getFirst() {
        return m_first;
    }
    public void setFirst(String first) {
        m_first = first;
    }
    ...
}
...
public class Run
{
    public static void main(String[] args) {
        Name name = new Name("Dennis", "Michael", "Sosnoski");
        Address address = new Address("1234 5th St.", "Redmond", "WA", "98052");
        Customer customer = new Customer(12345, name, address,
            "425 555-1212", "425 555-1213");
        System.out.println(customer);
    }
}

Finally, Listing 8 shows the console output from a test run (with the first line wrapped to fit):


Listing 8. Console output from test run (first line wrapped)

[dennis@notebook code]$ java -cp lib/asm-2.0.RC1.jar:lib/asm-commons-2.0.RC1.jar
  :lib/tostring-agent.jar:classes -javaagent:lib/tostring-agent.jar
  com.sosnoski.dwct.Run
Processing class sun/misc/URLClassPath$FileLoader$1
Processing class com/sosnoski/dwct/Run
Processing class com/sosnoski/dwct/Name
Modifying com/sosnoski/dwct/Name
Processing class com/sosnoski/dwct/Address
Modifying com/sosnoski/dwct/Address
Processing class com/sosnoski/dwct/Customer
Modifying com/sosnoski/dwct/Customer
Customer: #=12345
 Name: Dennis Michael Sosnoski
 Address: street=1234 5th St. city=Redmond state=WA zip=98052
 homePhone=425 555-1212 dayPhone=425 555-1213



Back to top


Conclusions

I've demonstrated how you can use ASM in combination with J2SE 5.0 annotations to perform automatic run time classfile modification. The ToString annotation I used as an example is interesting and (at least for me) somewhat useful. When used by itself, it doesn't much hinder the readability of the code. But if annotations are used for many different purposes (as will certainly be the case in the future, given how many Java extensions are being written or rewritten to make use of them), it seems likely that they will start to become intrusive in the code.

I'll return to this point in a later column when I look at the trade-offs between annotations and external configuration files. My personal view is that both have their uses, and although annotations were developed largely as an easier alternative to configuration files, separate configuration files are still appropriate in some cases. Just for the record, I think the ToString annotation is an example of an appropriate use!

One limitation of working with J2SE 5.0 extensions is that the JDK 1.5 compiler output can only be used with the JDK 1.5 JVM. For the next Classworking toolkit column, I'm going to look into a tool that gets around this limitation and show how the ToString implementation can be modified to work on older JVMs.




Back to top


Download

DescriptionNameSizeDownload method
Sample codej-cwt06075code.zip175 KBHTTP
Information about download methods


Resources



About the author

Photo of Dennis Sosnoski

Dennis Sosnoski is the founder and lead consultant of Seattle-area Java consulting company Sosnoski Software Solutions, Inc., specialists in J2EE, XML, and Web services support. His professional software development experience spans over 30 years, with the last several years focused on server-side Java technologies. Dennis is a frequent speaker on XML and Java technologies at conferences nationwide, and chairs the Seattle Java-XML SIG. Contact Dennis at dms@sosnoski.com.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top