Exception Advice: An Aspect-Oriented Modelby Barry Ruzek AbstractAn effective exception handling strategy is an architectural concern that transcends the boundaries of individual application components. Effective Java Exceptions outlines the Fault-Contingency exception model, which eliminates much of the confusion over using checked and unchecked exceptions in Java applications. Implementing the model using traditional Java techniques requires all components to follow a set of rules and behaviors. This implicit coupling between otherwise unrelated components leaves room for accidental lapses and failures. Applying aspect-oriented techniques to the Fault-Contingency exception model focuses the handling of this concern in one place, allowing other components to concentrate on their primary jobs. This article shows how an exception-handling aspect based on the Fault-Contingency exception model is an improvement over the traditional implementation. It offers a complete example of an exception-handling aspect created with AspectJ, an aspect-oriented extension to Java, to illustrate the concepts. The code supplied herein runs on both BEA WebLogic 9.2 and Tomcat 5.0 application servers. AOP and ArchitectureAs an application architect, you are responsible for making the decisions that govern how components relate to each other. Architectural decisions influence how components are designed, the patterns they use to collaborate, and the conventions they follow. If the decisions are sound, duly communicated, and followed by the project team, the result is a software system that is easy to understand, maintain, and extend. Everyone likes that, but it can be a challenge to achieve it. Architecture spans components, requiring them to perform certain actions or avoid particular behaviors so that everything harmonizes with an overall encompassing vision. Development teams are composed of human beings, and humans are not perfect. Even the best development teams have some trouble maintaining the purity of their architectural vision. There are two traditional countermeasures teams use to avoid architectural transgressions. The first is to set up regular reviews of design and code. The second is to build frameworks. Reviews are intended to catch problems as they emerge. Frameworks provide a reusable infrastructure whose constraints are intended to prevent problems from emerging in the first place. Aspect-oriented design is a third alternative for addressing architectural concerns. Instead of scattering architectural behavior throughout unrelated components, the behavior is encapsulated in an aspect and applied at specific execution points. Although work on aspect-oriented programming (AOP) began in the 1990s, it would be fair to say that widespread adoption is still some distance away. One reason may be a shortage of inspiring examples of how the technology can be of benefit. A compelling AOP example would have these traits:
The commonly used example of tracing method executions is a good way to illustrate what an aspect can do, but is not very inspiring—certainly not inspiring enough for most to invest in learning the technology or to make a case for using AOP in their next project. There are better examples out there, but you need to sift through all of the method-logging examples to find them. Exception handling in Java applications is a well-recognized concern in many software projects. Poorly managed exception discipline leads to fragile code that's difficult to understand and maintain. A consistent exception handling approach is a real benefit to most applications. Even when a team adopts an architectural model for exceptions, ensuring that every component adheres to the model requires effort and oversight. It seems like an exception handling model is a good candidate for an AOP exploration. Only you can judge whether or not it makes an inspiring example. The Fault-Contingency Exception ModelAn exception handling aspect starts with a model, or set of behaviors, that you want to apply across your application. The Fault-Contingency exception model provides a practical way of thinking about the exceptional conditions encountered by executing software. The model characterizes an anomalous outcome as either a Contingency or a Fault. A Contingency is an alternate outcome that can be described using the vocabulary of a component's intended purpose. Callers of a method or constructor have a strategy for handling its Contingency outcomes. A Fault, on the other hand, is a failure that cannot be described in terms of a semantic contract but only in terms of implementation details. For example, consider an Account Service with a A simple Java implementation of the Fault-Contingency exception model has three basic concepts: Contingency Exception, Fault Exception, and Fault Barrier. Methods and constructors use Contingency Exceptions to signal alternate outcomes that are part of their contracts. Contingency Exceptions are checked exceptions, so the compiler helps ensure that callers consider all contracted outcomes. Fault Exceptions are used to signal implementation-specific failures. Fault Exceptions are unchecked exceptions, and working code generally avoids catching them, leaving that responsibility to the class acting as Fault Barrier. The Fault Barrier's main responsibility is to craft a graceful exit to the processing activity when a Fault Exception reaches it. The graceful exit usually includes an indication of processing failure such as an apologetic display on the user interface (if there is one), or some other gesture indicating failure to the world "outside." A traditional implementation uses a subclass of
Components that participate in the model need to follow a set of conventions that make everything work. First, components must not throw exceptions other than Where the Traditional Implementation Falls ShortThe traditional implementation of the Fault-Contingency exception model is a nice improvement over ad hoc exception handling but is still some distance away from ideal. All components must adhere to the conventions, even if they have no other relationship to each other. The only way to ensure that they do is to review the code. A component may inadvertently catch Fault Exceptions, preventing them from reaching the Fault Barrier. If that happens, you can kiss your graceful exit goodbye and be left without a way to diagnose the fault. The traditional implementation places two obligations on the Fault Barrier. Its natural responsibility is to gracefully terminate the processing sequence. By virtue of its position near the top of the call stack, the Fault Barrier knows about the surrounding context and what constitutes an appropriate outward response. Its other obligation is to record analytic information associated with the fault so that people can figure out what happened. The only reason it has this job is that there is no other good place to do it. If a system requires more than one Fault Barrier (some do), then each must contain similar logic to capture the available information.
The ability to fix a problem depends on the quality of the information available. Practically speaking, the information that traditional implementation can supply is limited to what a
Aspects, Pointcuts, and AdviceAspect programming was invented to address problems like this. In our case, all the components in the application have to be concerned about the rules for faults and contingencies. If there is a lapse in a single class, its effect can ripple across any number of unrelated classes, causing the larger exception model to fail. Also, we have the Fault Barrier doing the job of recording analytic information when its natural role is simply to know how to generate a generic response to the external world and perform cleanup operations. The AOP idea is to encapsulate the required behaviors in a single entity: an aspect. An aspect contains logic that runs at certain well-defined points across the application. The logic that runs is called advice. The points at which advice is applied are called join points. You specify the sets of join points advice applies to by defining pointcuts. A pointcut is basically an expression that filters through all of the potential join points in your application, choosing some based on criteria such as the kind of join point, and various types of pattern matching. If you craft the aspect properly, it performs actions that would otherwise be scattered around the application. With everything in one place, the components in the rest of the application are free to concentrate on their primary jobs. The result is greater component cohesion, and that is a good thing. The sample application containing our exception-handling aspect is built using AspectJ, a superset of the Java language. The language supports the diversity of join points that are significant to exception handling across any application. Exceptions can be generated and caught in lots of ways during the execution of a Java application. Method executions are just one of them. An exception handling aspect also needs to worry about constructor execution, object initialization, and class initialization, which can all result in exceptions. It also needs to worry about places where exceptions are explicitly thrown and caught. AspectJ's pointcut language supports everything needed to implement the model we have in mind.
The ExceptionHandling AspectThe ExceptionHandling aspect is designed to be as flexible as possible so it has only two compile-time dependencies. They are the two Java classes that represent faults and contingencies:
The aspect assumes (and enforces) that the rest of the application uses these classes according to model rules. A nice feature of the AspectJ system is the ability to "program the compiler" to enforce a policy that transcends standard Java language rules. In our case, we want to encourage developers to think in terms of faults and contingencies and clearly distinguish between them. Since our architecture provides a
The ExceptionHandling aspect uses pointcuts to detect methods and constructors that declare checked exceptions other than those based on
package exception;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.Stack;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
public abstract aspect ExceptionHandling {
...
pointcut methodViolation():
execution(* *(..) throws (Exception+
&& !ContingencyException+
&& !RuntimeException+));
declare error:
methodViolation():
"Method throws a checked exception that is not
a ContingencyException";
pointcut constructorViolation():
execution(*.new(..) throws (Exception+
&& !ContingencyException+
&& !RuntimeException+));
declare error:
constructorViolation():
"Constructor throws a checked exception that is not
a ContingencyException";
...
}
Listing 1. Compile-time exception policy enforcement
In the example below, the Figure 1. Exception policy violation generates an error flag Exception Advice Join Points
The
Since the
public abstract aspect ExceptionHandling {
...
pointcut exceptionAdvicePoints():
(execution (* *.*(..))
|| execution (*.new(..))
|| initialization(*.new(..))
|| preinitialization(*.new(..))
|| staticinitialization(*))
&& !within(ExceptionHandling+)
&& !cflow(adviceexecution());
...
}
Listing 2. Exception advice points
Now, the Runtime Exception TranslationThe conventions of the Fault-Contingency model hold that any uncaughtThrowable should be considered a fault condition. In the traditional
implementation, there was no guarantee that every such exception would be caught and translated at lower levels, so the Fault Barrier had to be ready to catch any Throwable, not just FaultException. We can do better in the AOP implementation. We can guarantee that any uncaught Throwable thrown from one of our components is automatically translated to a FaultException. This makes the Fault Barrier implementation simpler and
ensures that any uncaught Throwable is treated as a fault by other advice within the ExceptionHandling aspect.
The "after throwing" advice runs if an execution sequence throws any sort of
public abstract aspect ExceptionHandling {
...
after() throwing(Throwable throwable):exceptionAdvicePoints(){
if (!(throwable instanceof FaultException ||
throwable instanceof ContingencyException)) {
throw new FaultException("Unhandled exception: ",
throwable);
}
}
...
}
Listing 3. Runtime exception translation
|
Article Tools Related Products Check out the products mentioned in this article:Related Technologies Related Articles Bookmark Article
|