Upgrades present risks to software systems, not least of which is inadvertent damage to features and data. Telecom systems, in particular, typically have stringent architectural availability and reliability quality attributes. Adding new telecom features is often fraught with difficulty--the same is true for other demanding areas such as flight simulation.
In this article, I'll use aspect oriented programming (AOP) as a means of intercepting Java methods that I want to upgrade. The intercepted code can then be added to the operational system to provide the required interim feature(s). For the next major release (following the upgrade), the upgrade code can then be moved out of the interception area into the main code base. This use of interception reduces the risk of updating the main class.
The intercepted code is set up using the following dynaop-based
script file. (Please see the dynaop project on java.net for more information.) This is a script file in the sense that it
annotates a class and prepares it for interception by another at
runtime. It's not an executable script file.
// Apply interceptor to all getter methods in the class
NMSSubject.
interceptor
(
NMSSubject.class,
GET_METHODS,
new UpgradeInterceptor()
);
The NMSSubject class is a client in the Observer
pattern; we want to upgrade the method getNewData(). The
UpgradeInterceptor class provides the AOP instrumentation
for intercepting getNewData(). Once intercepted, the
method can be augmented (to call into a JNDI naming service) and the
expanded data is returned transparently to the original client.
I'll make use of the Observer design pattern and upgrade a subclass method via AOP. This illustrates the merit of separating code for handling data production and consumption. An additional strand introduces, in passing, some software architecture concepts.
The Observer pattern (also known as publish-subscribe) is useful for designing code with a single server and multiple client classes. It implicitly encourages the separation of concerns between data producers and consumers. An example from network management is where a server application maintains a virtual picture of the topology of network of devices. It does this by a combination of polling (actively requesting) and (passively) listening for messages concerning device and link status. Figure 1 illustrates the idea with a topology server that communicates with a network of devices. A set of clients then takes a view of the topology server's data via the Observer pattern.

Figure 1. Topology Server and the Observer Pattern
The fidelity of the "picture" maintained by the server determines
the quality of the users' view of the managed network. This is an
important networking area, referred to as discovery. Many
device types support their own version of discovery; e.g., IP routers
work hard to maintain an up-to-date picture of the surrounding network.
If a distant link fails, then the routers must try to figure out the
effect this has on their internal tables. This process is called
convergence and can be thought of as a specific example of
the more general problem of discovery. For more on this and related
subject matter, please see Network
Management, MIBs & MPLS: Principles, Design &
Implementation.
Returning to Figure 1, the topology picture (or designated parts of it) can be communicated to the clients and kept up to date using the Observer pattern. In other words, when the topology server detects a change in the network, it communicates this to the clients using an appropriate method in the Observer pattern.
Let's take a look at an implementation of the observer.
My data producer (or publisher) is a class called
NMSSubject that implements an interface called
Subject.
interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(); }
As we can see, Subject has three methods that allow
observers to be added, removed, and notified (the notify is used for
updating observer data). These methods operate on a Collection object
in the NMSSubject class (the acronym "NMS" stands for
network management system).
public class NMSSubject implements Subject {
Collection observers = new ArrayList();
public void addObserver(Observer observer) {
observers.add(observer); }
public void removeObserver(Observer observer) {
this.observers.remove(observer); }
public void notifyObservers() {
for (Iterator i = observers.iterator(); i.hasNext();)
((Observer) i.next()).update(this); }
// Have AOP intercept the next call and in turn call into JNDI
// The legacy code might have been reading from a file or database.
// The intercepted code could call into JNDI or some other API
// This can be used as a temporary measure to avoid an upgrade
public String getNewData() {
return "Data Set: "; // Call into JNDI here via AOP }
}
The last method in NMSSubject is called
getNewData(), which is our target upgrade code. We want to
intercept calls to getNewData() via AOP and upgrade them
transparently, so that the calling client will not be aware of any
interception.
A single interface provides an update method, into which is passed an
instance of the NMSSubject class. This update method calls
into the getNewData() method of the subject class.
interface Observer {
void update(NMSSubject subject); }
public class NMSListener implements Observer {
public void update(NMSSubject subject) {
updateView(subject.getNewData()); }
public void updateView(String data) {
System.out.println("Updated subject data is " + data); }
}
The getNewData() method is intercepted for upgrade by a
class called UpgradeInterceptor. Let's look at this class.
|
Once we've intercepted the getNewData() method, its
return data can be augmented by a call into a JNDI naming service. The
call searches for a file named report.txt. This data is
then returned to the caller--NMSListener.update().
public class UpgradeInterceptor implements Interceptor
{
public Object intercept(Invocation invocation)
throws Throwable
{
String methodName = getFullMethodName(invocation);
System.out.println("Intercepted method name: " + methodName);
// Call the intercepted method.
Object result = invocation.proceed();
// Now call into JNDI to get some data
Lookup aLookup = new Lookup();
// Add this data to the intercepted method result
return (String) result + aLookup.directoryLookup("report.txt");
}
}
The Lookup class retrieves the required object
(report.txt) from the naming service using the following
code:
// Create the initial context
Context ctx = new InitialContext(env);
// Look up an object
Object obj = ctx.lookup(searchElement);
The object is then returned to the intercept method.
The code was written using Java Standard Edition version 1.5.0_01. The following four AOP-related .jar files are required and have to be placed in the classpath:
bsh-2.0b1.jarcglib-asm-1.0.jardynaop-1.0-beta.jarjakarta-oro-2.0.7.jarThese can be downloaded from the dynaop project's Documents and Files page.
They're included in a single .zip file called dynaop-1.0-beta.zip.
Remember to place the script file (mentioned at the start of
the article) in the same folder as the source code. You'll also have to
install JNDI and copy the file report.txt into the root
(e.g., D:\). Then compile these with the other Java files
and execute the program with the command:
java ObserverTest
This should generate the following output:
Intercepted method name: NMSSubject.getNewData()
Updated subject data is Data Set: E:\report.txt
Elapsed time in seconds 2
The first line indicates the interception and call into the upgrade code. The second line indicates the augmented data. The third line illustrates the time taken to run the program--an uninstrumented version of the program has an elapsed time of zero! This overhead is a downside to using the AOP technique.
We've now seen all of the elements required to implement an AOP-borne
upgrade. For the example above, I added the upgrade code in the
UpgradeInterceptor class. Let's say we had an
operational system that we wanted to upgrade (perhaps some serious bug
had just been discovered). Let's assume that the
ObserverTest class in the field has no AOP
instrumentation.
To upgrade the ObserverTest class, we have to copy the
following files:
ObserverTest
classdynaop script file dynaop.bsh.The next time the ObserverTest class is
instantiated, it will use interception, thereby facilitating the
upgrade.
|
Related Reading
AspectJ Cookbook |
Once the ObserverTest class has been upgraded in this
fashion, the fielded code no longer matches the compiled version--a potentially confusing state. A full release would be
advisable at some convenient time to re-align the source with the
released code. This technique should be used only in exceptional
circumstances--if at all! Also, there is a potentially
substantial time penalty incurred during the interception process.
Most successful software applications tend to suffer from feature creep--adding more code as each release unfolds. Some organizations still celebrate the number of lines of code their indefatigable programmers add as the product matures! Traditionally, this is sometimes referred to as "vertical development"--the software grows like an ever-expanding skyscraper.
A major benefit of AOP is that it allows for horizontal (rather than vertical) integration of new features. This powerful model facilitates a healthy degree of separation between legacy code and new features. Beyond this, AOP is an instrumentation technique that allows interfaces to be updated by intercepting code. This provides a useful mechanism for upgrading code as part of a minor or patch release. If the code has adhered to design principles such as separation of concerns, then the interception can be very effective at reducing risk. In this example, that separation was achieved with the Observer pattern, which provides a clear separation between data publishers and subscribers.
dynaop projectStephen B. Morris is an independent writer/consultant based in Ireland.
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.