Published on dev2dev (http://dev2dev.bea.com/)
 http://dev2dev.bea.com/pub/a/2007/06/exception-advice.html
 See this if you're having trouble printing code examples

Exception Advice: An Aspect-Oriented Model

by Barry Ruzek
07/04/2007

Abstract

An 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 Architecture

As 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 Model

An 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 getAccount() method that returns an Account object when supplied with an Account ID. You could easily imagine possible contingencies such as "No such account," or "Invalid Account ID," expressed in terms of the method's intended purpose. To imagine the possible faults, you would first have to know how the getAccount() method is implemented. Did it receive an SQLException because it could not connect to a database? Maybe there was a timeout waiting for a Web service that was down for maintenance. Or a missing file (that really ought to be there) caused a FileNotFoundException. The point here is that the caller of getAccount() should not know anything about the implementation and should not be forced to catch checked exceptions for any of its projected faults.

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 RuntimeException (say, FaultException) to represent Fault Exceptions. Being an unchecked exception, FaultException can be thrown without being explicitly caught or declared in method and constructor signatures. Therefore, it can remain under the radar until it is caught and handled by the Fault Barrier. Contingency Exceptions are based on a subclass of Exception (say, ContingencyException) that makes them subject to checking by the Java compiler. Since a ContingencyException is an integral part of a semantic contract, it makes sense to enlist the compiler’s help to ensure that a caller has a strategy for handling it.

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 FaultException or ContingencyException subclasses. Second, components must avoid catching FaultException, leaving that responsibility to the Fault Barrier. Components are responsible for handling exceptions thrown from external methods they invoke, performing translations to FaultException or ContingencyException, if needed. Any uncaught RuntimeException is considered to be a fault, and the Fault Barrier needs to be aware of this. These rules are simple and they go a long way toward eliminating messy, confusing exception sequences in application code. By cleanly separating faults and making them the responsibility of the Fault Barrier, the temptation for low-level code to handle fault conditions is greatly diminished. That leaves the field clear for Contingency Exceptions, which are expressly intended to convey meaningful information between components.

Where the Traditional Implementation Falls Short

The 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 RuntimeException can support: stack traces and error messages. Every Java programmer has experienced the joy of staring at a stack trace without a clue as to what really happened. Stack traces show what happened and where it happened but not why it happened. Ideally, you want to know which methods were called and how they were called—the types and values of arguments passed to each method leading up to the fault. Scattering code across every method to record its arguments upon entry is unpleasant, impractical, error prone, and a waste of effort unless a fault actually occurs.

Aspects, Pointcuts, and Advice

Aspect 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 Aspect

The 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 ContingencyException base class, we want to ensure that developers use its subclasses exclusively to represent contingency conditions. By doing so, we will prevent the temptation to declare that a method throws SQLException (for example), when the method should be treating any unexpected JDBC problem as a fault.

The ExceptionHandling aspect uses pointcuts to detect methods and constructors that declare checked exceptions other than those based on ContingencyException. Violations are flagged as compile errors, a sure way to draw attention to the problem.

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 commit() method of the Transaction class violates the exception policy by declaring that it throws SQLException, which should be considered to be a fault in its context. The compiler flags the violation as a compile error, based on the declaration in the ExceptionHandling aspect. The rollback() method conforms to the model by treating an unexpected SQLException as a fault, so no flag appears. The examples developed for this article were developed using Eclipse 3.2.1 with the AspectJ Development Tools (AJDT) 1.4.1 plug-in installed.

Exception Policy Violation Flagged as Compile Error
Figure 1. Exception policy violation generates an error flag

Exception Advice Join Points

The ExceptionHandling aspect uses the exceptionAdvicePoints() pointcut to apply advice to any execution sequence capable of throwing an exception. The aspect uses this pointcut several times to inject processing when exceptions might be thrown. The pointcut includes these join points:

Since the ExceptionHandling aspect has its own methods, has its own constructor, and undergoes class and object initialization, some of the join points selected above fall within the aspect itself. That's generally a bad thing, causing recursions when the aspect tries to advise its own advice. To avoid this possibility, the exceptionAdvicePoints() pointcut specifically excludes these join points from those selected above:

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 ExceptionHandling aspect is able to apply exception-related advice to all application components outside of itself. It won't try to apply its advice to methods that may run as a result of executing its own advice.

Runtime Exception Translation

The conventions of the Fault-Contingency model hold that any uncaught Throwable 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 Throwable. If the exception is a FaultException or ContingencyException, the advice takes no action. Otherwise, it replaces the offending exception with a new instance of FaultException, citing the uncaught exception as the cause. Note that the aspect's compile-time exception policy enforcement simplifies the check that the advice has to make.

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

Hitting the Fault Barrier

The ExceptionHandling aspect ensures that a FaultException always reaches the Fault Barrier, regardless of how the intervening code behaves. This guarantees that the Fault Barrier always gets to terminate the processing sequence in an orderly way. It frees the intervening code from worrying about inadvertently catching a FaultException. This advice makes these exceptions "slippery"; they cannot be caught by any handler outside of a Fault Barrier. The allHandlers() pointcut applies to every exception handler in the application and makes the class containing the handler and the exception being handled available to the before() advice logic. The advice executes before the code in the exception handler. The advice takes no action unless the exception is a FaultException. For a FaultException, the advice checks that the handler is within a class designated as a Fault Barrier. If so, the handler is allowed to catch the FaultException. If not, the FaultException is thrown again, bypassing the handler that was going to catch it. Eventually, the FaultException will reach a handler within a designated Fault Barrier class.

public abstract aspect ExceptionHandling {
  ...

  pointcut allHandlers(Object handlerType, Throwable throwable):
          handler(Throwable+) && args(throwable) && 
          this(handlerType);

  before(Object handler, Throwable throwable):
      allHandlers(handler, throwable) {
      if (throwable instanceof FaultException) {
          if (!(isFaultBarrier(handler))) {
              FaultException fault = (FaultException) throwable;
              throw (fault);
          }
      }
  }

  abstract boolean isFaultBarrier(Object exceptionHandler);

  ...
}
Listing 4. Only the Fault Barrier catches faults

How does the aspect know if a handler is within a designated Fault Barrier? One way to accomplish it would be to hard-code the class names of designated Fault Barrier classes into the ExceptionHandling aspect. But that would tie the aspect to a particular application. To make the ExceptionHandling aspect as flexible as possible, it declares an abstract method to answer the Fault Barrier question. The implementation of isFaultBarrier() is supplied in a subaspect that knows the specifics of the application and is able to judge if a handler object is a Fault Barrier or not. That means that ExceptionHandling must be declared as an abstract aspect. It must be extended by a concrete subaspect before any of its advice will be activated. The subaspect needs to supply only an implementation for isFaultBarrier() plus one other method that will be discussed next.

Better Fault Diagnostics

The ExceptionHandling aspect described so far ensures that every uncaught exception thrown by the application will arrive at the Fault Barrier as a FaultException. This applies to unexpected exceptions from Java library methods, accidental exceptions caused by bugs in application code, and FaultExceptions explicitly thrown when fault conditions are discovered. The Fault Barrier needs to catch only the FaultException instead of any Throwable, as required by the traditional implementation. Application code can be structured in any way that seems natural, without considering how it might affect the application's fault handling capacity.

That is a nice advantage of the aspect-oriented approach. But the ExceptionHandling aspect really proves its worth in the quality of diagnostic information it can provide when a fault occurs. An aspect can observe the entire application as it runs. The ExceptionHandling aspect uses this ability to trace the arguments passed to every method and constructor in the application. When a fault occurs, the aspect appends a special Application Trace section to the standard exception and stack trace information it records. Each item in the Application Trace describes the type of processing, the name of the class, method, or constructor, and the names, types, and values of the arguments used to invoke it. The result looks like this:

FATAL : exception.ServiceExceptionHandling - Application Fault Detected
exception.FaultException: Unexpected failure on catalog query: com.ibm.db2.jcc.b.SQLException: The string constant beginning with "'" does not have an ending string delimiter.
at domain.CatalogDAO.performQuery(CatalogDAO.java:86)
at domain.CatalogDAO.getCatalogEntries(CatalogDAO.java:57)
at domain.CatalogService.getCatalogEntry(CatalogService.java:16)
at domain.CartItem.<init>(CartItem.java:22)
at domain.ShoppingCart.addToCart(ShoppingCart.java:28)
at action.SelectItemAction.performAction(SelectItemAction.java:44)
at action.BaseAction.execute(BaseAction.java:57)
at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:484)
at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:274)
at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1482)
at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:525)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:763)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:856)
at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:225)
at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:127)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:283)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:175)
at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3214)
at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:121)
at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:1983)
at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:1890)
at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1344)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:209)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:181)

Application Trace:
method-execution: domain.CatalogDAO.performQuery(query:java.lang.String=SELECT * FROM ADMINISTRATOR.CATALOG WHERE CATALOGID = 'BAD'INPUT', transaction:integration.Transaction=jdbc:db2://localhost:50000/DOCMGMT)
method-execution: domain.CatalogDAO.getCatalogEntries(catalogID:java.lang.String=BAD'INPUT, transaction:integration.Transaction=jdbc:db2://localhost:50000/DOCMGMT)
method-execution: domain.CatalogService.getCatalogEntry(catalogID:java.lang.String=BAD'INPUT)
constructor-execution: domain.CartItem.<init>(catalogID:java.lang.String=BAD'INPUT, quantity:int=1)
initialization: domain.CartItem.<init>(catalogID:java.lang.String=BAD'INPUT, quantity:int=1)
method-execution: domain.ShoppingCart.addToCart(catalogID:java.lang.String=BAD'INPUT, quantity:int=1)
method-execution: action.SelectItemAction.performAction(cart:domain.ShoppingCart=Cart0024988, action:org.apache.struts.action.ActionMapping=ActionConfig[path=/SelectItem,name=SelectItemForm,scope=session,type=action.SelectItemAction, form:org.apache.struts.action.ActionForm=CatalogID-BAD'INPUT Quantity-1, request:javax.servlet.http.HttpServletRequest=Http Request: /ShoppingServices/SelectItem.do, response:javax.servlet.http.HttpServletResponse=weblogic.servlet.internal.ServletResponseImpl@22a6c2)
method-execution: action.BaseAction.execute(action:org.apache.struts.action.ActionMapping=ActionConfig[path=/SelectItem,name=SelectItemForm,scope=session,type=action.SelectItemAction, form:org.apache.struts.action.ActionForm=CatalogID-BAD'INPUT Quantity-1, request:javax.servlet.http.HttpServletRequest=Http Request: /ShoppingServices/SelectItem.do, response:javax.servlet.http.HttpServletResponse=weblogic.servlet.internal.ServletResponseImpl@22a6c2)
Listing 5. The result of the Application Trace

The Application Trace contains only those methods that are subject to the ExceptionHandling aspect's influence: the methods that are specifically part of our application. Notice that the items in the Application Trace correspond roughly to the entries at the top of the stack trace. (The bottom half of the stack trace covers classes that are part of the WebLogic Server implementation.) The example here comes from a Struts application that permitted a user to supply a form parameter (BAD'INPUT) containing a single quote character that caused a syntax error in an SQL statement. Having the argument values visible in the diagnostic recording helps determine when things went wrong. There is a good bit of code in the ExceptionHandling aspect revolving around fault recording. To begin, take a look at how the aspect controls how faults are recorded.

public abstract aspect ExceptionHandling {
  ...

  private boolean FaultException.logged = false;

  private boolean FaultException.isLogged() {
      return this.logged;
  }

  private void FaultException.setLogged() {
      this.logged = true;
  }

  after() throwing(FaultException fault): exceptionAdvicePoints(){
      if (!fault.isLogged()) {
          logFault(fault);
          fault.setLogged();
      }
  }

  ...
}
Listing 6. Fault logging advice

The after-throwing advice runs whenever a FaultException is thrown from any point in the application. Its job is to invoke the aspect's logFault() method, which does the actual recording. A single fault may trigger the advice multiple times as it propagates through all of the methods on the call stack, so the advice needs a way to know when the recording has already been done. To do this, it uses another AOP technique: member introduction. The aspect introduces a boolean flag into the FaultException type along with methods to access it. The flag and methods are appropriately marked private; they are invisible outside of the ExceptionHandling aspect. The overall effect is that diagnostic recording happens immediately after a fault occurs and at no other time.

A fault can occur at any instant. To prepare for that eventuality, the ExceptionHandling aspect needs to track activity as the application runs. That way, it is ready to record the sequence of calls leading to the fault, along their argument values, should a fault occur. To do this, the aspect maintains a per-thread stack of JoinPoint object references. The aspect's trace stack grows and shrinks in parallel with the call stack as the application executes. The AspectJ runtime makes JoinPoint objects available to advice logic through the language construct thisJoinPoint. JoinPoint objects contain dynamic context information for advice logic, allowing the logic to learn specifics about the situation that triggered the advice.

public abstract aspect ExceptionHandling {
  ...
 
  private static ThreadLocal<Stack<JoinPoint>> traceStack = 
                         new ThreadLocal<Stack<JoinPoint>>() {
      protected Stack<JoinPoint> initialValue() {
          return new Stack<JoinPoint>();
      }
  };
 
  private static void pushJoinPoint(JoinPoint joinPoint) {
      traceStack.get().push(joinPoint);
  }
 
  private static JoinPoint popJoinPoint() {
      Stack<JoinPoint> stack = traceStack.get();
      if (stack.empty()) {
          return null;
      } else {
          JoinPoint joinPoint = stack.pop();
          return joinPoint;
      }
  }
 
  private static JoinPoint[] getJoinPointTrace() {
      Stack<JoinPoint> stack = traceStack.get();
      return stack.toArray(new JoinPoint[stack.size()]);
  }
 
  ...
}
Listing 7. ThreadLocal call tracing methods

With these methods in place, the advice that tracks application calls is very simple. The join points that are pushed onto the stack are those identified by the exceptionAdvicePoints() pointcut—any execution sequence that can terminate abruptly due to an exception. Before the sequence starts, the JoinPoint object is pushed onto the thread's trace stack. After the sequence is complete, its JoinPoint object is popped from the stack. The JoinPoint objects in the trace stack are never dereferenced unless a fault occurs.

public abstract aspect ExceptionHandling {
	...

    before(): exceptionAdvicePoints(){
        pushJoinPoint(thisJoinPoint);
    }

    after(): exceptionAdvicePoints(){
        popJoinPoint();
    }

	...
}
Listing 8. Call tracing advice

When a fault happens, the advice in Listing 6 runs, invoking the methods below to render diagnostics. The information comes from the stack trace contained in the FaultException and from the aspect's own per-thread stack of join points. The formatJoinPoint() method extracts what we want from each JoinPointobject: the qualified method or constructor name, the names and types of its formal parameters, and the values passed as arguments for those parameters.

public abstract aspect ExceptionHandling {
  ...
 
  private void logFault(FaultException fault) {
      ByteArrayOutputStream traceInfo = 
                                 new ByteArrayOutputStream();
      PrintStream traceStream = new PrintStream(traceInfo);
      fault.printStackTrace(traceStream);
      StringBuffer applicationTrace = new StringBuffer();
      JoinPoint[] joinPoints = getJoinPointTrace();
      for (int i = joinPoints.length - 1; i >= 0; i--) {
          applicationTrace.append("\n\t"
                          + formatJoinPoint(joinPoints[i]));
      }
      recordFaultDiagnostics("Application Fault Detected"
                      + "\n" + traceInfo.toString()
                      + "\nApplication Trace:"
                      + applicationTrace.toString());
  }
 
  abstract void recordFaultDiagnostics(String diagnostics);
 
  private String formatJoinPoint(JoinPoint joinPoint) {
      CodeSignature signature = (CodeSignature) 
                                  joinPoint.getSignature();
      String[] names = signature.getParameterNames();
      Class[] types = signature.getParameterTypes();
      Object[] args = joinPoint.getArgs();
      StringBuffer argumentList = new StringBuffer();
      for (int i = 0; i < args.length; i++) {
          if (argumentList.length() != 0) {
              argumentList.append(", ");
          }
          argumentList.append(names[i]);
          argumentList.append(":");
          argumentList.append(types[i].getName());
          argumentList.append("=");
          argumentList.append(args[i]);
      }
      StringBuffer format = new StringBuffer();
 
      format.append(joinPoint.getKind());
      format.append(": ");
      format.append(signature.getDeclaringTypeName());
      format.append(".");
      format.append(signature.getName());
      format.append("(");
      format.append(argumentList);
      format.append(")");
      return format.toString();
  }
 
}
Listing 9. Fault logging methods

The ExceptionHandling aspect defines the abstract method recordFaultDiagnostics() to allow an application to specify how it wants to record the diagnostic information produced by the aspect. An application supplies an implementation for that method in a concrete subaspect. This arrangement keeps the recording specifics out of the base aspect, keeping it as flexible as possible.

The ability of an aspect to observe the rest of the application enables it to furnish these extended diagnostics when a fault arises. It does this job without the knowledge or cooperation of other application components. Concentrating the code that implements the concern of diagnostic reporting in one place is one of the benefits of the aspect-oriented approach.

Conclusion

The Fault-Contingency exception model makes sense for lots of Java applications. Implementing the concerns of the model using AOP techniques has some nice advantages. The ability to detect departures from the model at compile time is one of them. Isolating the logic related to fault processing in one place is another. Thinking in terms of Faults and Contingencies eliminates lots of confusing code in your application. Thinking in terms of aspects makes the application code even simpler, reducing the chance of inadvertent errors creeping into code. Is this inspiring enough to consider AOP? Only you can decide.

Download

References


Barry Ruzek has been named a Master Certified IT Architect by the Open Group. He has over 30 years of experience developing operating systems and enterprise applications.


Return to Dev2Dev.