ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Peeking Inside the Box: Attribute-Oriented Programming with Java 1.5, Part 1 Peeking Inside the Box: Attribute-Oriented Programming with Java 1.5, Part 1

by Don Schwarz
06/30/2004

I recently spent an afternoon at the London Science Museum watching steam engines, pumps, turbines, and other antique machines operate. Some were so simple that their behavior was obvious, but most had sections cut away and replaced with glass windows so their inner workings could be observed. As I walked through the exhibits, I couldn't help but wonder how the craftsmen and maintenance crews of those machines were able to do their jobs without the cut-away models and diagrams that accompanied the exhibits here. Software engineers have it easy!

To a seasoned Java developer with access to source code, any program can look like the transparent models at the museum. Tools such as thread dumps, method call tracing, breakpoints, and profiling statistics can provide insight into what a program is doing right now, has done in the past, and may do in the future. But in a production environment, things aren't always so obvious. These tools are not typically available, or at the very least, they are usable only by trained developers. Support teams and end users also need to be aware of what an application is doing at any given time.

To fill this void, we've invented simpler alternatives such as log files (typically for server processes) and status bars (for GUI applications). However, because these tools must capture and report only a very small subset of the information available to them and often must present this information in a way that is easy to understand, they tend to be programmed explicitly into the application. This code is intertwined with the application's business logic in such a way that developers must "work around it" when trying to debug or understand core functionality, yet remember to update this code whenever that functionality changes. What we really want to do is to centralize the status-reporting logic in a single place, and manage the individual status messages as metadata.

In this article, I will consider the case of a status-bar component embedded in a GUI application. I will explore a number of different ways to implement this status reporter, starting with the traditional hard-coded idiom. Along the way, I will introduce and discuss a number of new features in Java 1.5, including annotations and run-time bytecode instrumentation.

The StatusManager

My primary goal is to create a JStatusBar Swing component that can be embedded in our GUI application. Figure 1 shows what the finished status bar looks like in a simple JFrame.

Figure 1
Figure 1. Our dynamically generated status bar

Since I do not wish to reference any GUI components directly from our business logic, I'll also create a StatusManager to act as an entry point for status updates. The actual notification will be delegated out to a StatusState object so that later this can be extended to support multiple concurrent threads. Figure 2 shows this arrangement.

Figure 2
Figure 2. StatusManager and JStatusBar

Now I need to write code which that call the methods on StatusManager to report on the progress of the application. These method calls are typically scattered throughout the code in try-finally blocks, often one per method.

public void connectToDB (String url) {
    StatusManager.push("Connecting to database");
    try {
        ...
    } finally {
       StatusManager.pop();
    }
}

The code in the 01_original directory in the peekinginside-pt1.tar.gz sample code (see References below) exhibits this traditional approach.

This code is functional, but after duplicating it dozens or even hundreds of times throughout your codebase, it can begin to look a bit messy. In addition, what if I want to access these messages in some other way? Later in this article, I'll define a user-friendly exception handler that shares the same messages. The problem is that I've hidden the status message in the implementation of our method, rather than on the interface where it belongs.

Attribute-Oriented Programming

What I really want to do is to leave any references to StatusManager out of our code altogether and simply tag the method with our message. I can then use either code-generation or run-time introspection to do the real work. The XDoclet Project refers to this as Attribute-Oriented Programming, and provides a framework which can convert custom Javadoc-like tags into source code.

However, with the inclusion of JSR-175, Java 1.5 has provided a more structured format for including these attributes inside of real code. The attributes are called "annotations" and they can be used to provide metadata for class, method, field, or variable definitions. They must be declared explicitly, and provide a sequence of name-value pairs that can contain any constant value (including primitives, strings, enumerations, and classes).

Annotations

To hold our status message, I'll want to define a new annotation that contains a string value. Annotation definitions look a lot like interface definitions, but the keyword @interface is used instead of interface and only methods are supported (though they act more like fields).

public @interface Status {
    String value();
}

Like an interface, I will put the @interface in a file named Status.java, and import it into any other files that need to reference it.

value may seem like a strange name for our field here. Something like message might be more appropriate; however, value has special meaning to Java. This allows us to declare annotations as @Status("...") rather than @Status(value="..."), as a shortcut.

I can now define my method as follows:

@Status("Connecting to database")
public void connectToDB (String url) {
    ...
}

Note that to compile this code I needed to use the -source 1.5 option. If you're using Ant (as our code samples do) rather than building a javac command line directly, you'll also need Ant 1.6.1.

In addition to classes, methods, fields, and variables, annotations can also be used to provide metadata for other annotations. In particular, Java comes with a few annotations that can be used to customize how your annotation works. Let's redefine our annotation as follows:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Status {
    String value();
}

The @Target annotation defines what the @Status annotation can reference. Ideally, I would like to mark up arbitrary blocks of code, but the only options are methods, fields, classes, local variables, parameters, and other annotations. I'm only interested in code, so I choose METHOD.

The @Retention annotation allows us to specify when Java is free to discard this information. It can be either SOURCE (discard when compiling), CLASS (discard at class load time), or RUNTIME (do not discard). Let's start with SOURCE, but I will need to upgrade it later.

Instrumenting Source Code

Now that my messages are encoded in metadata, I'll need some code to notify the status listeners. Let's assume for the moment that I continue to store my connectToDB method in source code control without any references to StatusManager. However, before compiling the class, I want to add in the necessary calls. That is, I want to insert the try-finally statements and the push/pop calls automatically.

The XDoclet framework is a source code generation engine for Java that uses annotations similar to those described above, but stored in Java source code comments. XDoclet works very well for generating entire Java classes, configuration files, or other build artifacts, but does not support the modification of existing Java classes. This limits its usefulness for instrumentation. Instead, I could use a parser tool like JavaCC or ANTLR (which comes with a grammar for parsing Java source code), but that requires a fair amount of effort.

There seems to be no good tool available for doing source code instrumentation of Java code. There may be a market for such a tool, but as you will see in the rest of this article, bytecode instrumentation can be a much more powerful technique.

Pages: 1, 2

Next Pagearrow