Pioneered at Xerox's Palo Alto Research Center (PARC) in the late '80s and early '90s, aspect-oriented programming (AOP) has greatly influenced modern software development, from the latest research at IBM, to the tag-driven development nature of Microsoft's .NET and C# environments. Indeed, AOP has been gaining an even wider audience of late: its research community is extremely active, a number of open source projects offer a wealth of implementations, and, as a natural complement to object-oriented programming (OOP), developers are discovering that AOP provides for more intuitive, extensible, and flexible middleware. If you're an enterprise software developer, the combination of JDK 1.5, the newest release of Java, and JBoss AOP, available in JBoss 4, may just be the best thing since sliced bread.
An aspect is a common feature that's typically scattered across methods, classes, object hierarchies, or even entire object models. For example, metrics is one common aspect. To capture useful benchmarks from your application, you have to sprinkle timing (often liberally) throughout your code.
However, metrics are something that your class or object model really shouldn't be concerned about. After all, metrics -- and, as you'll see, logging, persistence, caching, security, and other system heavyweights -- is irrelevant to your actual application: it doesn't represent a customer or an account, and it doesn't realize a business rule. It's simply orthogonal.
In AOP, a feature like metrics is called a crosscutting concern, as it's a behavior that "cuts" across multiple points in your object model, yet is distinctly different. As a development methodology, AOP recommends that you abstract and encapsulate crosscutting concerns.
For example, let's say you want to add code to an application to measure the amount of time it takes to invoke a particular method. In plain Java, that code would look something like Listing One.
Listing One: A bank account object with metrics added public class BankAccountDAO |
While this code works, there are a few problems with its approach:
1. The profiling code really doesn't belong sprinkled throughout your application. It makes your code bloated and harder to read, as you have to enclose the timings within a try/finally block.
2. It's extremely difficult to turn metrics on and off, as you have to manually add the code in the try/finally block to each and every method or constructor you want to benchmark.
3. If you wanted to expand your metrics to include a method or failure count, or even to register these statistics to a more sophisticated reporting mechanism, you'd have to modify a lot of different files (again).
All in all, this approach to metrics is very difficult to maintain, expand, and extend, because it's dispersed throughout your entire code base. And this is just a tiny example!
As an alternative, aspect-oriented programming allows you to add behavior such as metrics "around" your code. For example, AOP provides you with programmatic control to specify that you want calls to BankAccountDAO to go through a metrics aspect before executing the actual body of that code.
Creating Aspects in JBoss AOP
In short, all AOP frameworks define two things: a way to implement crosscutting concerns and a programmatic construct -- a programming language or a set of tags -- to specify how you want to apply those snippets of code.
Let's take a look at how JBoss AOP, available in JBoss 4.0 (http://www.jboss.org) implements its cross-cutting concerns, and see how you can implement a metrics aspect in JBoss.
The first step in creating a metrics aspect in JBoss AOP is to encapsulate the metrics feature in its own Java class. Listing Two extracts the try/finally block in Listing One's Bank AccountDAO.withdraw() method into Metrics, an implementation of a JBoss AOP Interceptor class.
Listing Two: Implementing metrics in a JBoss AOP Interceptor 01. public class Metrics implements org.jboss.aop.Interceptor |
Under JBoss AOP, the Metrics class wraps withdraw(). When calling code invokes withdraw(), the AOP framework breaks the method call into its parts and encapsulates those parts into an Invocation object. The framework then calls any aspects that sit between the calling code and the actual method body. Then, when the AOP framework is done dissecting the method call, it calls Metric's invoke() method at line 3. Line 8 wraps and delegates to the actual method and uses an enclosing try/finally block to perform the timings. Line 13 obtains contextual information about the method call from the Invocation object, while line 14 displays the method name and the calculated metrics.
With metrics code within its own object, you can easily expand and capture additional measurements later on.
Now that metrics are encapsulated into an aspect, let's see how to apply it.
Applying Aspects in JBoss AOP
To apply an aspect, you specify when to execute the aspect code. The points in execution are called pointcuts. (Pointcuts are sometimes called interceptors as well. See the sidebar "Aspects or Interceptors?" to learn why.)
For example, a pointcut might be defined as "for all calls to the JDBC method executeQuery(), call the aspect that verifies SQL syntax."
Defining pointcuts is what AOP is mostly about. An entry point could be a field access, or a method or constructor call. An event could be an exception being thrown.
Some AOP implementations use languages akin to queries to specify pointcuts. Others use tags. JBoss AOP uses both. Listing Three shows how to define a pointcut for the metrics example.
Listing Three: Defining a pointcut for Listing Two 1. <bind pointcut="public void com.mc.BankAccount DAO->withdraw(double amount)"> |
Lines 1-3 define a pointcut that applies the metrics aspect only to BankAccountDAO.withdraw(). Lines 4-6 define a general pointcut that applies the metrics aspect to all methods in all classes in the com.mc.billing package.
JBoss AOP has a rich set of pointcut expressions that you can use to apply your aspects. You can attach your aspects to a specific Java class in your application or you can use more complex compositional pointcuts to specify a wide range of classes within one expression.
Benefits of AOP
With AOP, as this example shows, you're able to pull together crosscutting behavior into one object and apply it easily and simply, without polluting and bloating your code with features that ultimately don't belong mingled with business logic. Instead, common crosscutting concerns can be maintained and extended in one place.
Notice too that the code within the BankAccountDAO class has no idea that it's being profiled. This is what aspect-oriented programmers deem orthogonal concerns.
Profiling is an orthogonal concern. In the OOP code snippet in Listing One, profiling was part of the application code. With AOP, you can remove that code.
A modern promise of middleware is transparency, and AOP (pardon the pun) clearly delivers.
Just as important, orthogonal behavior could be bolted on after development. In Listing One, monitoring and profiling must be added at development time. With AOP, a developer or an administrator can (easily) add monitoring and metrics as needed without touching the original code.
This is a very subtle but significant part of AOP, as this separation (obliviousness, some may say) allows aspects to be layered on top of or below the code that they cut across. A layered design allows features to be added or removed at will. For instance, perhaps you snap on metrics only when you're doing some benchmarks, but remove it for production. With AOP, this can be done without editing, recompiling, or repackaging the code.
A unique feature of the JBoss AOP framework, even among other AOP frameworks, is the capacity to dynamically reshape flow and add aspects to objects. With JBoss AOP, you can turn on metrics at any time, as JBoss supports hot-deployment of aspects at runtime.
Extending the Java language
The Java Developers Kit version 1.5 (JDK 1.5) is due out this summer and is currently in beta. (See page 18 for details.) You can download it for Linux already.
One interesting new feature being introduced to Java is metadata, the ability to define metadata about a class, method, field, or constructor through typesafe annotations. (See the feature "Metadata for Java" on page 26 for a detailed article on metadata.) Metadata can provide additional information about Java constructs, much like synchronized and transient provide in the current grammar.
Long story short, you can tag a Java class, method, field, or constructor and have an aspect be triggered by the application of that tag. This amounts to "pluggable" Java keywords.
For example, let's apply the metrics aspect via metadata. This code...
public class BankAccountDAO
{
@profiled public void withdraw(double
amount)
{
// Actual method body...
}
}
... uses the new metadata feature of JDK 1.5 to tag the withdraw() method for profiling. You can then define a pointcut to apply metrics to any method that is @profiled:
<bind pointcut="* *->@profiled(..)">
<interceptor class="com.mc.Metrics"/>
</bind >
Given this framework, you can define your own annotations to trigger the application of aspects. In effect, you can extend the Java language.
Someone once pointed out that if OOP defines the words used to code, then, in a very linguistic sense, AOP introduces the grammar for adjectives. JBoss ships with a prepackaged set of adjectives that you can use to describe your application. Any object can be "remote," "cached," "persistent," "transactional," or "secure."
In fact, this sort of coding is known as tag driven development, because the simple usage of tags allows you to apply aspects to a class. For those of you familiar with C# and .NET, there are similar constructs in that system. For instance, .NET's @remote allows you to code a class and tag it to become a web service.
Aspect-Oriented Middleware
Just as GUIs helped formulate the early patterns of OOP, middleware is shaping up to be the "killer app" of aspect-oriented programming. The features provided by middleware, by nature, are crosscutting: the features are common across object hierarchies and are orthogonal to business logic.
For example, think about EJB: EJB defines APIs and XML for developers to interact with system services. You define an EJB, use the interface, and provide the XML, and the system provides remoting, persistence, caching, transactional demarcation, and security.
AOP is perfect for middleware, as middleware features can be applied to plain old java objects (POJOS) after-the-fact, without changing the code or design of the existing business model. AOP lets system designers and users get one step closer to "transparent nirvana." Packaging up middleware into a set of aspects is great for application developers as it frees them to focus on writing the POJOS that make up their application's specific behavior rather than forcing them to work under an API dictated to them by their system architecture or even by a standards body. Plain Java objects can run anywhere: within an application server, within an applet, and within simple unit tests.
JBoss 4 slices and dices J2EE features and serves them a la carte to your object model rather than going through the sometimes cumbersome and unnecessary process of implementing an EJB. Take the example of transactions.
Returning to the BankAccountDAO Java class, suppose that the withdraw() method has to update an account database table and insert a record into a transaction log for auditing. withdraw() is distinctly transactional, and it would be nice if the developer could define it just so.
JBoss AOP allows you to enable transactions within any Java class in your object model by simply specifying a metadata tag.
public class BankAccountDAO
{
@transaction (trans-attribute=Required)
public void withdraw(double amount)
{
// Actual method body
}
}
Here, the @transaction tag triggers a pre-packaged pointcut provided with the JBoss 4 application server. When withdraw() is invoked, a transaction is started and is either automatically committed when withdraw() finishes or is rolled back if an exception is thrown.
The pointcut is defined simply as:
<bind pointcut="all(@transaction)">
<interceptor factory="org.jboss.aop.tx.
TxInterceptorFactory"/>
</bind>
@transaction can be applied to any method, static or member, as well as any constructor.
For BankAccountDAO, it may also be useful to apply security so that only an authenticated and authorized user can invoke withdraw(). JBoss AOP also allows you to apply role-based security to any field, method, or constructor, using the enterprise security modules already available in the JBoss application server. The application of the security aspect is a little bit different than the one for transactions, as you usually don't embed security information in classes. So, JBoss AOP has the ability to define metadata in an XML format. JBoss 4 calls this crosscutting metadata.
1. <annotation tag="security" class="com.mc.BankAccountDAO">
2. <security-domain>java:/jaas/ldap </security-domain>
3. <method-permission>
4. <role-name>teller</role-name>
5. <method>
6. <method-name>withdraw </method-name>
7. </method>
8. </method-permission>
9. </annotation>
This annotation applies a security aspect to the withdraw() method. The security-domain on line 2 specifies the JBoss security repository, where information about users, roles, and passwords are stored. Lines 3-8 define the role that can invoke withdraw(). (If you're an EJB developer, you'll likely notice that this is the same syntax as an EJB deployment descriptor. This syntax has been extended to support the declaration of security for fields and constructors as well.)
To optimize access to your database, you might want to store the BankAccountDAO within a cache, so that read-only operations like getAccountBalance(), getAccountStatus(), and getCustomerAddress() are readily processed. JBoss AOP provides a transparent transactional and cluster-aware cache that can be used with plain Java objects.
1. cache = new org.jboss.cache.aop. TreeCacheAop();
2. config = new PropertyConfigurator();
3. config.configure(tree, "META-INF/replSync-service.xml");
4.
5. BankAccountDAO account = ...;
6. cache.putObject(account.getAccountNumber(), account);
7. tx.begin()
8. account.setCustomerAddress("55 Street Road");
9. account.withdraw(500.00);
10. tx.commit()
Line 6 simply puts the account object within the cache. It automatically becomes replicated across a cluster and transactional. Lines 8-9 are updates to the account object that happen within the transaction. The account object's in-memory transactional state (the Java fields) is isolated from any other transaction. On transaction commit, the changed Java fields within the transaction become "official" and are automatically replicated to the rest of the cluster. There are both optimistic and pessimistic locking schemes that can be used to ensure the integrity of the in-memory data. This requires no changes to the BankAccountDAO code.
Generalized Containers
AOP is a new paradigm for expanding code re-use and easing the maintenance of complex code. AOP provides guidelines for encapsulating code that is typically scattered throughout your object model into one set of objects, and provides a simple mechanism to integrate new code at a later time. When combined with JDK 1.5's new metadata feature, AOP can even be used to extend the Java language.
The first killer application of AOP is middleware. AOP allows application code to be freer from middleware APIs, making application developers more productive. Standalone, tag-like constructs offer developers a trivial way to interact with the system and ask for services for their POJOs. EJB is already a fixed set of system services, but JBoss AOP is a more intuitive approach to applying these services and is much more flexible to user requirements.
The complexity of middleware is slowly dissolving, and as more and more aspects are defined and implemented, things will become even simpler. One day we will all walk through walls.
Aspects or Interceptors?
To implement aspects, you might imagine a rules engine matching conditions and generating indirection in execution. Execution proceeds as normal until the rules engine finds a match, which causes control to be interrupted and transferred to the AOP framework. This is why aspects are sometimes called interceptors: code execution is intercepted via indirection.