A Tour of AOP
Frameworks such as servlets and EJB offer specific solutions to a focused set of problems. For example, the servlet specification offers a framework to deal with requests made using the HTTP protocol. Given that each framework deals with a specific problem, it may also provide some solutions for dealing with common crosscutting concerns in that problem space. For example, the servlet framework provides basic support for intercepting HTTP requests.
Similarly, the EJB framework addresses a wide set of crosscutting concerns such as transaction management and security. In the EJB3 specification, it even provides limited support for interceptors, which, to an extent, match the goals of AOP. However, as we will see later, it falls short of being a complete solution.
Note that you may use AOP along with an underlying framework. In such an arrangement, the core framework deals with the target problem and lets aspects deal with crosscutting concerns. For example, the core Spring framework deals with dependency injection for configuration and enterprise service abstraction to isolate beans from the underlying infrastructure details, while employing AOP to deal with crosscutting concerns such as transaction management and security.
Framework approaches to crosscutting concerns often, but not always, boil down to either employing some form of code generation or implementing appropriate design patterns. Let’s examine the two in more details.
Code generation techniques shift some responsibility of writing code from the programmer to the machine. Of course, programmers do have to write code for the generators themselves. These techniques represent powerful ways to deal with a wide range of problems and often are helpful in raising the level of abstraction. They can modularize crosscutting concerns by modifying the original code such as adding observer notifications or producing additional artifacts, such as automatic proxy classes, thus taking care of one of the drawbacks of a direct use of design patterns—manual modifications in many places.
A variation of code-generation works at the compiled code level. Instead of producing source code that needs to be compiled into machine code, the code generator directly produces machine code. For Java, the difference between source code-level generation and byte-code generation is rather small, given how directly source code maps to byte-code.
In Java 5, the annotation language feature allows attaching additional information to the program elements. Code generation techniques can take advantage of those annotations to produce additional artifacts (such as Java code or XML configuration files). Java 5 even provides a tool, the Annotation Processing Tool (APT), to simplify the process. However, APT forces understanding low-level details such as the syntax tree and that makes it difficult for a programmer to use it unless he acquires specific skills. It is no surprise that you hardly see many non-framework programmers using APT. AOP, on the other hand, can provide simpler solutions to process annotations, as we will see in rest of the book.
Many systems, most notably AspectJ, use byte-code manipulation as the underlying technique in implementing AOP. The difference is how it employs the technique as a part of the overall AOP model. First, it provides a much simpler programming model making it easier for a developer to create modularized crosscutting implementations without knowing low-level details such as the abstract syntax tree. It essentially provides a domain-specific language (DSL) targeted towards crosscutting concerns. The user is isolated from byte-code manipulation mechanisms as well, which is not for the faint of heart. Furthermore, by limiting power, it nudges developers towards writing better code. In short, while code-generation is capable of doing anything AspectJ can do (and a whole lot more), AspectJ brings a level of discipline that is essential to good software engineering when it comes to dealing with crosscutting concerns.
Design patterns provide well-understood solutions to recurring problems. Some of the recurring problems are due to crosscutting concerns. This should be a big surprise, since while AOP is only about 10 years old, the crosscutting problem existed for as long as we have been creating software systems. In this section, we take a comparative view of some of the design patterns—observer, chain of responsibility, decorator and proxy, as well as interceptor—that help with crosscutting concerns. You will see that there are quite a few similarities and in fact, you can view a few design patterns as a “poor man’s” AOP implementation.
A classic technique in UI programming is to respond to events such as mouse move and key pressed. For example, you may show a dialog box when a key is pressed. Even on server-side enterprise applications, message-oriented architectures involve sending and responding to messages. The observer design pattern forms the basis for the event programming to decouple the event source (the subject) from the event responder (the observer). When a subject changes its state, it notifies all observers of the change by calling a method such as notify<ChangeType>(), passing it an event object that encapsulates the change.
The notification method iterates over all the observers and calls a method on each (in message-oriented systems, these details change a bit, but the overall scheme remains the same). The called method in the observer includes the logic appropriate to respond to the event. The key point of the observer pattern is that the subject knows nothing about which particular objects are observing it. The pattern also supports an arbitrary number of observers.
AOP's advice may superficially look like an event responder. However, there are some important differences. First, there is no explicit firing of “events” in AOP. In other words, you won't see invocations such as notify<ChangeType>() thus decoupling of all observer pattern logic from the subject class. Second, the context collected by pointcuts (equivalent to information carried by an “event” object) is a lot more flexible and powerful in AOP. Pointcuts can collect just the right amount of context needed for advice logic. With a typical event model, you end up passing "everything that you might possibly need".
CHAIN OF RESPONSIBILITY
The chain of responsibility (COR) pattern, as shown in figure 3, puts a chain of processing objects in front of a target object. Before or after invoking the command object, processing objects may perform additional work or interrupt the chain.
Successful use of the COR pattern has two prerequisite: there is only one (or a small number) of target methods and the associated framework already supports the pattern. For example, the filter implementation in the Servlet framework implements the COR pattern. It works well there because both prerequisites are met: it targets only one method—doService() and the filter management code is implemented as a part of the framework itself. In this setup, some coarse grained crosscutting concerns—ones that deal at HTTP request level—may be modularized into servlet filters. However, anything that needs to go beyond the doService() method, filter offers no solution.
AOP works in similar ways, except it doesn’t have either of the prerequisites. Instead, each aspect simply deals with the problem head on by advising appropriate code.