IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Java technology
developerWorks
AOP banishes the tight-coupling blues
code87 KBe-mail it!
Contents:
Overview of AOP
What is crosscutting?
Creating static crosscuts
The implementation scenario
The design challenge
Static crosscutting to the rescue
Let's test it!
Refactoring the example
Conclusion
Resources
About the author
Rate this article
Related content:
Improve modularity with aspect-oriented programming
Test flexibly with AspectJ and mock objects
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
Build highly decoupled systems with the power of static crosscutting

Level: Intermediate

Andrew Glover (mailto:aglover@vanwardtechnologies.com?cc=&subject=AOP banishes the tight-coupling blues)
CTO, Vanward Technologies
18 February 2004

Many Java developers have embraced the non-intrusive style and flexibility of aspect-oriented programming (AOP), particularly when it comes to building highly decoupled and extensible enterprise systems. In this article, you'll see for yourself how one of AOP's functional design concepts -- static crosscutting -- can turn what might be a tangled mass of tightly coupled code into a powerful, extensible enterprise application.

Aspect orientation is a powerful principle to draw upon during the fast-paced cycle of transforming business requirements into software features. By shifting the primary design focus away from the traditional hierarchical nature of object-oriented programming (OOP), AOP and design principles allow software architects to contemplate design in a horizontal, yet complementary, manner to object orientation.

In this article, you'll learn how to implement one of the most underused features of AOP. Crosscutting is a relatively simple design and programming technique that packs a hearty punch, particularly when it comes to building loosely coupled, extensible enterprise systems. While dynamic crosscutting -- in which the runtime behavior of objects can be altered -- is considered one of the foundations of AOP, static crosscutting is a far less known technique. In this article, I'll attempt to remedy that. I'll start with an overview of both dynamic and static crosscutting, and then move quickly into an implementation scenario that demonstrates the latter technique. You'll see for yourself how handily static crosscutting meets one of the most common enterprise challenges: how to keep an application's codebase flexible while leveraging third-party code.

Please note that although I will begin with a brief, conceptual overview of aspect-oriented programming, this article is not intended as an introduction to AOP. See the Resources section for a listing of introductory articles on this topic.

Overview of AOP
The fundamental beauty of object-oriented design lies in its ability to model real-world domain entities and their respective behavior as abstract objects. Systems designed in an object-oriented manner yield effective business objects, such as Persons, Accounts, Orders, and Events. The downside of object-oriented design is that such business objects can become muddied with mixed properties and operations that are incongruent with the object's original intent.

Aspect-oriented programming effectively addresses this problem by allowing designers to add behavior to objects in a non-obtrusive, clean, modularized manner through the use of dynamic and static crosscutting.

What is crosscutting?
Crosscutting is a term specific to aspect-oriented programming. It refers to the act of cutting across the established divisions of responsibility (such as logging and performance optimization) in a given programming model. Within the world of crosscutting, there are two types: dynamic crosscutting and static crosscutting. In this article, I am concerned with static crosscutting, although I will briefly discuss both.

Dynamic crosscutting
Dynamic crosscutting is the process of creating behavior in an aspect via pointcuts and join points, which can then be applied laterally, at execution time, to existing objects. Dynamic crosscutting is commonly used to facilitate the addition of logging or authentication to various methods in an object hierarchy. Let's take a minute to learn about some of the concepts at work in dynamic crosscutting:

  • An aspect is analogous to a class in the Java programming language. Aspects define pointcuts and advices and are compiled by aspect compilers, such as AspectJ, to interweave crosscuts (both dynamic and static) into existing objects.

  • A join point is a precise point of execution in a program, such as a method found in a class. For instance, the method bar() in object Foo could be a join point. Join point is an abstract notion; one does not actively define a join point.

  • A pointcut is in essence a construct to capture join points. For instance, one could define a pointcut to capture any call to method bar() on object Foo. In contrast to join points, pointcuts are defined in aspects.

  • An advice is executable code for a pointcut. A commonly defined advice is the addition of logging facilities, where a pointcut captures every call to bar() on object Foo and the advice dynamically inserts some logging functionality, such as capturing bar()'s parameters.

These concepts are central to dynamic crosscutting, although as we'll see they are not all required for static crosscutting. See Resources to learn more about dynamic crosscutting.

Static crosscutting
Static crosscutting differs from dynamic crosscutting in that it does not modify the execution behavior of a given object. Rather, static crosscutting allows you to alter the structure of an object by introducing additional methods, fields, and properties. Moreover, static crosscutting lets you affix extensions and implementations to the fundamental structure of an object.

While there are currently no common uses of static crosscutting to speak of -- it appears to be a relatively unexplored (although powerfully compelling) feature of AOP -- the implications of the technique are enormous. With static crosscutting, architects and designers can effectively model complex systems in a true object oriented manner. Static crosscutting lets you plug in common behaviors across a system without having to create deep hierarchies, in essence rendering object models more elegant and true to their real-life structures.

For the remainder of the article I will focus on the technique and application of static crosscutting.

Creating static crosscuts
The syntax for creating static crosscuts is quite different from that of dynamic crosscutting, in that there are no pointcuts or advices. Given an object (such as Foo, defined below), static crosscutting makes it quite simple to create new methods, add additional constructors, and even alter the inheritance hierarchy. We'll use an example to better see how static crosscutting can be implemented in an existing class. Listing 1 shows a simple, unaspected Foo.

Listing 1. An unaspected Foo

public class Foo {
   
   public Foo() {
     super();
   }	

}

Adding a new method to an object is as simple as defining one in an aspect, as shown in Listing 2.

Listing 2. Adding a new method to Foo

public aspect FooBar {
  
   void Foo.bar() { 
     System.out.println("in Foo.bar()");	
   }

}

Constructors are slightly different in that the new keyword is required, as shown in Listing 3.

Listing 3. Adding a new constructor to Foo

public aspect FooNew {
   
   public Foo.new(String parm1){
     super();
     System.out.println("in Foo(string parm1)");
   }

}

Changing the inheritance hierarchy of an object requires a declare parents tag. For example, in order to become multi-threaded, Foo would need to implement Runnable, or extend Thread. Listing 4 shows how you might use the declare parents tag to change Foo's inheritance hierarchy.

Listing 4. Changing Foo's inheritance hierarchy

public aspect FooRunnable {

   declare parents: Foo implements Runnable;
  
   public void Foo.run() {
      System.out.println("in Foo.run()");
   }

}

At this point, you can probably begin to imagine for yourself the implications of static crosscutting, particularly with regard to creating loosely coupled, highly extensible systems. In the sections that follow, I'll walk you through a real-world design and implementation scenario that demonstrates the ease with which static crosscutting can be used to extend the flexibility of your enterprise applications.

The implementation scenario
Enterprise systems are often designed to take advantage of third party products and libraries. So as not to couple the entire architecture to the desired product, it is common to include an abstraction layer in applications designed to interface with outside vendor code. This abstraction layer furnishes the architecture with a high degree of flexibility to plug in another vendor's implementation or even homegrown code with minimal disruption to the system's contiguity.

For this implementation example, let's imagine a system that, upon some action taken, notifies customers via disparate communication channels. The example system employs an Email object to represent an instance of a direct email communication. The Email object contains properties such as the sender's address, the recipient's address, a subject heading, and a message body, as shown in Listing 5.

Listing 5. The example Email object

public class Email implements Sendable {
   private String body;
   private String toAddress;
   private String fromAddress;
   private String subject;

   public String getBody() {
	return body;
   }

   public String getFromAddress() {
	return fromAddress;
   }

   public String getSubject() {
	 return subject;
   }

   public String getToAddress() {
	 return toAddress;
   }

   public void setBody(String string) {
	 body = string;
   }

   public void setFromAddress(String string) {
	 fromAddress = string;
   }

   public void setSubject(String string) {
	 subject = string;
   }

   public void setToAddress(String string) {
  	 toAddress = string;
   }
}

Incorporating third-party code
Rather than build a custom communication system that sends e-mails, faxes, SMS messages, etc., the architecture team decides to incorporate a vendor product that is able to send messages based on arbitrary objects following a specific convention. The vendor product is quite flexible and provides a mapping mechanism, via XML, that permits custom client objects to be mapped to the vendor's specific channel implementations. The vendor system relies heavily on this mapping file and the Java platform's reflection abilities to work with normal Java objects.

Embracing flexibility, the architecture team models a Sendable interface, as shown in Listing 6.

Listing 6. The example Sendable interface

public interface Sendable {
   String getBody();
   String getToAddress();
}

Figure 1 shows a class diagram of the Email object and the Sendable interface.

Figure 1. Class diagram of Email and Sendable
Class Diagram of Email and Sendable

The design challenge
In addition to the ability to send various message formats on disparate channels, the vendor offers a hook to allow for recipient address validation via a provided interface. The vendor's documentation indicates that any object found to implement this interface will follow a predefined life cycle, which guarantees the validateAddress() method will be called and handled correctly corresponding to the resulting behavior. If validateAddress() returns false, the vendor's communication system will not attempt to send the corresponding communication. Listing 7 shows the vendor's Validatable interface.

Listing 7. Address validation for the Sendable interface

package com.acme.validate;

public interface Validatable {
   
   boolean validateAddress();

}

Using fundamental object-oriented design principles, the architecture team may decide to update the Sendable interface to extend the vendor's Validatable interface. This decision, however, would lead to a direct dependency and coupling to the vendor's code. If the team decides, down the road, to go with another vendor's tool, the code base would have to be refactored to remove the extends clause in the Sendable interface and the implemented behavior in the object hierarchy.

A more elegant and ultimately more flexible solution is to employ static crosscutting to add the behavior to desired objects.

Static crosscutting to the rescue
Using aspect-oriented principles, the team could create an aspect that declares the Email object implements the vendor's Validatable interface; moreover, in the aspect, the team would then code the desired behavior in the validateAddress() method. This leads to a nice decoupling of the code as the Email object does not contain any imports of the vendor packages, nor does it define a validateAddress() method. The Email object has no idea it is of type validatable! Listing 8 shows the resulting aspect where the Email object is statically enhanced to implement the vendor's Validatable interface.

Listing 8. Email validatable aspect

import com.acme.validate.Validatable;

public aspect EmailValidateAspect {

   declare parents: Email implements Validatable;

   public boolean Email.validateAddress(){
     if(this.getToAddress() != null){
	   return true;
     }else{
	   return false;
     }
   }
}

Let's test it!
You can utilize JUnit to demonstrate that the EmailValidateAspect actually altered the Email object. In a JUnit test suite, an Email object can be created with default values and a series of test cases can verify that Email is indeed an instance of Validatable; moreover, a test case can assert that if the toAddress is null, a call to validateAddress() will return false. Additionally, a test case can verify that a non nulltoAddress will cause validateAddress() to return true.

1-2-3, testing with JUnit
You can start by creating a fixture that constructs an instance of an Email object with simple values. Notice in Listing 9 that the instance does have a valid (meaning it is not null) toAddress value.

Listing 9. JUnit setUp()

import com.acme.validate.Validatable;

public class EmailTest extends TestCase {
 private Email email;

 protected void setUp() throws Exception {
   //set up an email instance
   this.email = new Email();
   this.email.setBody("body");
   this.email.setFromAddress("dev@dev.com");
   this.email.setSubject("validate me");
   this.email.setToAddress("ag@ag.com");
 }

 protected void tearDown() throws Exception {
   this.email = null;
 }

//EmailTest continued...
}

With a valid Email object, testEmailValidateInstanceof() ensures the instance is of type Validatable, as shown in Listing 10.

Listing 10. JUnit instanceof check

public void testEmailValidateInstanceof() throws Exception{
  
  TestCase.assertEquals("Email object should be of type Validatable", 
    true, this.email instanceof Validatable);		 
}

The next test case, shown in Listing 11, purposely sets the toAddress field to null and then verifies that the call to validateAddress() returns false.

Listing 11. JUnit null toAddress check

public void testEmailAddressValidateNull() throws Exception{
   
   //force a false
   this.email.setToAddress(null);

   Validatable validtr = (Validatable)this.email;	
   
   TestCase.assertEquals("validateAddress should return false", 
      false, validtr.validateAddress());		
}

The last step is for sanity's sake: the testEmailAddressValidateTrue() test case calls validateAddress() with the Email instance's initial values where the toAddress field was set to ag@ag.com.

Listing 12. JUnit non null toAddress check

public void testEmailAddressValidateTrue() throws Exception{		
   
   Validatable validtr = (Validatable)this.email;	

   TestCase.assertEquals("validateAddress should return true", 
      true, validtr.validateAddress());		
}

Refactoring the example
The architecture team worked hard to abstract the communication implementations with the Sendable interface; however, the team's first attempt seemingly ignored this interface. Taking the lessons learned with static crosscutting the Email object, the team further refines its strategy by moving the contractual behavior up the hierarchy into the Sendable base interface.

The new aspect creates an extension of the Sendable interface with the vendor's Validatable interface. Furthermore, the implemented behavior is then created in the aspect. This time, the validateAddress() method is defined for another communication object: Fax, as shown in Listing 13.

Listing 13. A better aspect

import com.acme.validate.Validatable;

public aspect SendableValidateAspect {
	
   declare parents: Sendable extends Validatable;

   public boolean Email.validateAddress(){
     
     if(this.getToAddress() != null){
       return true;
     }else{
       return false;
     }
   }

   public boolean Fax.validateAddress(){
 
     if(this.getToAddress() != null 
	    && this.getToAddress().length() >= 11){

        return true;
     }else{
        return false;
     }
  }
}

Never stop refactoring
You may note that the aspect in Listing 13 suffers slightly in that all implementers of Sendable have their validateAddress() method defined in a single aspect. This could easily lead to code bloat. Additionally, altering the static structure of an interface can have undesirable side effects if not approached with caution: One must ensure that all implementers of the target interface are found. So the lesson here is simple: Never stop refactoring.

Conclusion
While the API example is contrived, it has hopefully demonstrated how simple it can be to apply static crosscutting in an enterprise architecture. Static crosscutting is particularly effective when it comes to the type of scenario described here (where it can be used to unobtrusively alter an object's behavior, and even its definition), but it has many other uses. For example, you might use static crosscutting to "EJB-ify" POJOs (plain old Java objects) upon deployment; or you might use it in business objects to leverage the lifecycle interfaces of persistence frameworks such as Hibernate (see Resources).

Whatever the application, static crosscutting provides an elegant solution to many of the ailments that can render enterprise code ineffective. With this article you have learned the basics of the technique and one of its most fundamental uses. See Resources to learn more about aspect-oriented programming and different crosscutting techniques.

Resources

  • Download the source code used in this article.

  • You can download AspectJ and its associated tools from eclipse.org/aspectj. The site also hosts an FAQ, mailing lists, excellent documentation, and links to other resources on AOP. It's a good place to begin further research.

  • AspectWerkz is dynamic, lightweight and high-performant AOP/AOSD framework for the Java platform.

  • The Eclipse IDE features an AspectJ plugin.

  • For a comprehensive source of information about aspect-oriented software development, try AOSD.net.

  • The JBoss team has created an interesting AOP framework.

  • Hibernate is a powerful, ultra-high-performance object/relational persistence and query service for the Java platform.

  • Codehaus is a great repository of interesting open source projects, including AspectWerkz and Nanning (another Aspect implementation for the Java platform).

  • Visit the Developer Bookstore for a comprehensive listing of technical books, including Aspect-Oriented Programming with AspectJ by Ivan Kiselev (Sams Publishing, 2002) and AspectJ in Action by Ramnivas Laddad (Manning Publishing, 2003), as well hundreds of other Java-related titles.

  • You'll find articles about every aspect of Java programming in the developerWorks Java technology zone.

  • Also see the Java technology zone tutorials page for a complete listing of free Java-focused tutorials from developerWorks.

About the author
Andrew Glover is the CTO of Vanward Technologies, a Washington, DC, metro area company specializing in the construction of automated testing frameworks, which lower software bug counts, reduce integration and testing times, and improve overall code stability.


code87 KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Comments?



developerWorks > Java technology
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact