An Exception Handling Framework for J2EE Applications(onjava)

来源:互联网 发布:dis mac-add 编辑:程序博客网 时间:2024/05/01 12:05
 

An Exception Handling Framework for J2EE Applications
by ShriKant Vashishtha
01/11/2006
In most Java projects, a large percentage of the code is boilerplate code. Exception handling comes under this category. Even though the business logic may be just three or four lines of code, exception handling might go on for ten to 20 lines. This article talks about how to keep exception handling simple and straightforward, keeping the developer's plate clean for him to concentrate on business logic rather than devoting time to writing exception-handling boilerplate code. It also gives the basis and guidelines to create and deal with exceptions in the J2EE environment and targets some of the business problems, in which exceptions could be used to resolve them. This article uses the Struts framework as the presentation implementation, though the approach is applicable to any presentation implementation.
When Do We Need Checked and Unchecked Exceptions?
Have you ever wondered why it is such a pain to put a try-catch block around a block of code you have written, even if you know that you cannot do much about those exceptions and will be content with just logging them in the catch block? You may wonder why this can't just be logged in a centralized place, which in most cases for a J2EE application is a front controller. In other words, you would like to not be bothered with them, as you don't have much to do with them. But what if a method signature contains a throws clause? You are either forced to catch these exceptions or put them in a throws clause of your own method. That's a pain! Fortunately, the Java APIs have a category of exceptions called unchecked exceptions, which you are not forced to catch. Still, the question is, on what basis do you decide which exceptions should be checked and which unchecked? Here are some guidelines:
  • Exceptions for which the end user cannot take any useful action should be made unchecked. For example, exceptions that are fatal and unrecoverable should be made unchecked. There is no point in making XMLParseException (thrown while parsing an XML file) checked, as the only action to be taken may be to fix the root cause based on the exception trace. By extending java.lang.RuntimeException, one can create custom unchecked exceptions.
  • Exceptions associated with the user's actions in an application should be made checked. Checked exceptions require the client to catch them. You might ask why we don't make every exception unchecked. The problem might be that some of them may not get caught where they should be. It creates a bigger problem as errors are identified at runtime only. Examples of checked exceptions are business validation exceptions, security exceptions, etc.
Related Reading
Java Enterprise Best Practices
By The O'Reilly Java Authors
Table of Contents
Index
Sample Chapter

Read Online--Safari Search this book on Safari:
 

Code Fragments only
Exception Throwing Strategy
Catch a base application exception (say, BaseAppException) only and declare in your throws clause
In most of J2EE applications, decisions about what error message to show on which screen against an exception get made in the presentation layer only. This brings up another question: why shouldn't we put this decision making in a common place? In J2EE applications, a front controller is a centralized place to do common handling.
Also, there has to be a common mechanism to propagate the exceptions. Exceptions need to be handled in a generic way, too. To deal with this, we always need to catch the base application exception BaseAppException at the controller end. This means we need to put the BaseAppException, and only that exception, in the throws clause of each method that could throw a checked exception. The concept is to use polymorphism to hide the actual implementation of the exception. We just catch BaseAppException in the controller, but the specific exception instance thrown might be any of several derived exception classes. You get a lot of exception-handling flexibility using this approach:
  • You don't need to put so many checked exceptions in the throws clause. Only one exception is required in throws clause.
  • No more clutter of catch blocks for application exceptions. If we need to deal with them, one catch block (for BaseAppException) is sufficient.
  • You don't need to do exception handling (logging and getting the error code) yourself. That abstraction will be taken care of by ExceptionHandler, which will be discussed later.
  • Even if you introduce more exceptions into the method implementation at later stages, the method signature doesn't change, and hence requires no change in the client code, which otherwise would have to be changed in a chain reaction. However thrown exceptions need to be specified in the Javadoc of the method so that the method contract is visible to the client.
 
Here is a sample of throwing a checked exception.
public void updateUser(UserDTO userDTO) 
  throws BaseAppException{
    UserDAO userDAO = new UserDAO();
    UserDAO.updateUser(userDTO);
    ...
    if(...)
        throw new RegionNotActiveException(
            "Selected region is not active");
}
 
Controller Method:
...
try{
    User user = new User();
    user.updateUser(userDTO);
}catch(BaseAppException ex){
    //ExceptionHandler is used to handle
    //all exceptions derived from BaseAppException
}
...
So far we have discussed that all methods that could potentially throw checked exceptions and are called by Controller should contain only BaseAppException in their throws clauses. However, this actually implies that we can't have any other application exception in the throws clause. But what if you need to perform some business logic based on a certain type of exception in the catch block? For handling those cases, a method may also throw a specific exception. Keep in mind that this is a special case and should not be taken for granted by developers. The application exception in question again should extend the BaseAppException. Here is an example:
CustomerDAO method:
//throws CustomerNotActiveException along with
//BaseAppException
public CustomerDTO getCustomer(InputDTO inputDTO)
    throws BaseAppException,
        CustomerNotActiveException {
    . . .
    //Make a DB call to fetch the customer 
    //details based on inputDTO
    . . .
    // if not details found
    throw new CustomerNotActiveException(
        "Customer is not active");
}
 
Client method:
 
//catch CustomerNotActiveException
//and continues its processing
public CustomerDTO getCustomerDetails(
 UserDTO userDTO)
    throws BaseAppException{
    ...
    CustomerDTO custDTO = null;
    try{
        //Get customer details 
        //from local database
        customerDAO.getCustomerFromLocalDB();
    }catch(CustomerNotActiveException){
        ...
        return customerDAO
            .activateCustomerDetails();
    }
}
Handle unchecked exceptions at the web application level
All unchecked exceptions should be handled at the web application level. A web page can be configured in web.xml to be displayed to the end user whenever any unchecked exception occurs in the application.
 
Wrap third-party exceptions into application-specific exceptions
Whenever an exception originates from other external interfaces (components), it should be wrapped in an application-specific exception and handled accordingly.
Example:
 
try {
    BeanUtils.copyProperties(areaDTO, areaForm);
} catch (Exception se) {
    throw new CopyPropertiesException(
        "Exception occurred while using 
            copy properties", se);
}
Here, CopyPropertiesException extends java.lang.RuntimeException, as we would just like to log it. We are catching Exception instead of catching the specific checked exceptions that the copyProperties method could throw, since for all of those exceptions we're throwing the same unchecked CopyPropertiesException.
Too Many Exceptions
You may be wondering, if we create an exception for each error message, could we run into in an overflow of exception classes themselves? For instance, if "Order not found" were an error message for OrderNotFoundException, you certainly wouldn't like to have CustomerNotFoundException for an error message "Customer not found" for an obvious reason: the two exceptions represent the same thing, and differ only in the contexts in which they are used. So if we could specify the context while handling the exception, we could certainly reduce these exceptions to just one RecordNotFoundException. Here is an example:
try{
    ...
}catch(BaseAppException ex){
    IExceptionHandler eh =ExceptionHandlerFactory
      .getInstance().create();
    ExceptionDTO exDto = eh.handleException(
        "employee.addEmployee", userId, ex);
}
Here, the employee.addEmployee context will be appended to the error code of a context-sensitive exception, to make a unique resultant error code. For instance, if the error code for RecordNotFoundException is errorcode.recordnotfound, the resultant error code for this context will become errorcode.recordnotfound.employee.addEmployee, which will be a unique error code for this context.
However, there is a caveat: if you are using multiple interfaces in the same client method and they all could throw RecordNotFoundException, it would be really difficult to know for which entity you got this exception. In cases where business interfaces are public and can be used by various external clients, it's recommended to use specific exceptions only and not a generic exception like RecordNotFoundException. Context-specific exceptions are really useful for DB-based recoverable exceptions where exception classes are always the same and the difference comes only in the contexts in which they occur.
Exception Hierarchy for a J2EE Application
As discussed earlier, we need to define a base exception class, say BaseAppException, that contains the default behavior of all application exceptions. We'll just put this base class in throws clause of every method that potentially throws a checked exception. All checked exceptions for an application should be the subclasses of this base class. There are various ways of defining error-handling abstractions. However, these differences should have more to do with business cases and not technical compulsions. The abstraction of error-handling can be divided into the following categories. All of these exceptions classes will derive from BaseAppException.
 
Checked Exceptions
  • Business exceptions: Exceptions that occur while performing business logic. BaseBusinessException is the base class for this category.
  • DB exceptions: Exceptions thrown while interacting with persistence mechanism. BaseDBException is the base class for this category.
  • Security exceptions: The exceptions that come while performing security operations. The base class for this type of exceptions will be BaseSecurityException.
  • Confirmation exceptions: Used for getting a confirmation from the end user for executing a specific task. The base class for this category is BaseConfirmationException.
Unchecked Exceptions
  • System exception: There are times when you would like to use unchecked exceptions. For instance, you may not want to handle exceptions coming from third-party library APIs. Rather, you would like to wrap them in unchecked exceptions and throw to the controller. At times, there are configuration issues, which again cannot be handled by the client and should be raised as unchecked exceptions. All custom unchecked exceptions should extend from the java.lang.RuntimeException class.
Presentation-Level Exception Handling
The presentation layer is uniquely responsible for deciding the action to be taken for an exception. That decision-making involves identifying the error code based on the exception thrown. Also, we need to know to which screen we should redirect after handling the error.
We need an abstraction for getting the error code based on the type of exception. This should also perform logging when needed. We'll call this abstraction ExceptionHandler. It's a façade based on the "Gang of Four" (GOF) Facade pattern (which the Design Patterns book says is used to "provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.") for the whole exception handling subsystem that handles all of the exceptions derived from BaseAppException. Here is the sample exception handling in a Struts Action method.
 
 
try{ 
    ... 
    DivisionDTO storeDTO = divisionBusinessDelegate 
      .getDivisionByNum(fromDivisionNum); 
}catch(BaseAppException ex){ 
    IExceptionHandler eh = ExceptionHandlerFactory 
      .getInstance().create(); 
    String expContext = "divisionAction.searchDivision"; 
    ExceptionDTO exDto = eh.handleException( 
        expContext , userId, ex); 
    ActionErrors errors = new ActionErrors(); 
        errors.add(ActionErrors.GLOBAL_ERROR 
          ,new ActionError( 
          exDto.getMessageCode())); 
    saveErrors(request, errors); 
    return actionMapping.findForward( 
        "SearchAdjustmentPage"); 
}
If you take a closer look at the exception handling code we just wrote, you might realize that you are writing similar code for each and every Struts method, which is a pain again. The objective is to remove boilerplate code as much as possible. We need to abstract it out again.
The solution is to use the Template Method design pattern (from GOF: "It is used to implement invariant parts of an algorithm once, and to leave it to subclasses to implement parts of the algorithm that can vary."). We need a base class that will contain the algorithm in the form of Template Method. The algorithm will contain the try-catch block for BaseAppException and a call to a dispatchMethod the method implementation (delegated to derived class) as shown below in a Struts based Action:
public abstract class BaseAppDispatchAction 
  extends DispatchAction{
...
protected static ThreadLocal 
  expDisplayDetails = new ThreadLocal();
 
public ActionForward execute(
 ActionMapping mapping,
 ActionForm form,
  HttpServletRequest request,
 HttpServletResponse response) throws Exception{
    ...
    try{
        String actionMethod = request
           .getParameter(mapping.getParameter());
        finalDestination =dispatchMethod(mapping,
          form, request, response,actionMethod);
    }catch (BaseAppException Ex) {
        ExceptionDisplayDTO expDTO = 
           (ExceptionDisplayDTO)expDisplayDetails
               .get();
        IExceptionHandler expHandler = 
            ExceptionHandlerFactory
                .getInstance().create();
        ExceptionDTO exDto = expHandler
            .handleException(
                expDTO.getContext(), userId, Ex);
        ActionErrors errors = new ActionErrors();
        errors.add(ActionErrors.GLOBAL_ERROR, 
            new ActionError(exDto
                .getMessageCode()));
        saveErrors(request, errors);
        return mapping.findForward(expDTO
            .getActionForwardName());
    } catch(Throwable ex){
           //log the throwable
           //throw ex;
    } finally {
           expDisplayDetails.set(null);
    }
In Struts, the DispatchAction::dispatchMethod method is used to forward the request to the appropriate Action method, named actionMethod.
 
Let's say you get searchDivision as the actionMethod from an HTTP request: dispatchMethod will dispatch the request to a searchDivision method in the derived Action class of BaseAppDispatchAction. Here, you can see that exception handling is done only in the base class, and the derived class just implements Action methods. It confirms to the Template Method design pattern, where the exception-handling part remains invariant while the actual implementation (the varied part) of dispatchMethod is deferred to the derived class.
The modified Struts Action method mentioned earlier will look something like this after the above changes.
 
... 
String exceptionActionForward = 
    "SearchAdjustmentPage"; 
String exceptionContext = 
    "divisionAction.searchDivision"; 
        
ExceptionDisplayDTO expDTO = 
    new ExceptionDisplayDTO(expActionForward, 
        exceptionContext); 
expDisplayDetails.set(expDTO); 
... 
DivisionDTO divisionDTO =divisionBusinessDelegate 
   .getDivisionByNum(fromDivisionNum); 
...
Wow! Now it looks clean. As exception handling is being done in one centralized place (BaseAppDispatchAction), the scope of manual errors is also minimized.
However, we need to set the exception context and the name of the ActionForward to which the requests will be forwarded if there's an exception. And we are setting this in a ThreadLocal variable, expDisplayDetails.
Hmm. Fine. But why a java.lang.ThreadLocal variable? The expDisplayDetails is a protected data member in the BaseAppDispatchActiion class, and that's why it needs to be thread-safe too. The java.lang.ThreadLocal object comes to the rescue here.
Exception Handler
We talked about an abstraction for handling exceptions in the last section. Here is the contract it should satisfy.
  • Identify the type of exception and get the corresponding error code, which could be used to display a message to the end user.
  • Log the exception. The underlying logging mechanism is hidden and could be configured based on some environmental properties.
As you might have noticed, the only exception we are catching in the presentation layer is BaseAppException. As all checked exceptions are subclasses of BaseAppException, implicitly, we are catching all of the derived classes of BaseAppException. It's fairly easy to identify the error code based on the name of the class.
//exp is an object of BaseAppException
String className = exp.getClass().getName();
Error codes can be configured in an XML file (named exceptioninfo.xml) based on the name of the exception class. Here is a sample of exception configuration.
<exception name="EmployeeConfirmationException">
    <messagecode>messagecode.laborconfirmation</messagecode>
    <confirmationind>true</confirmationind>
    <loggingtype>nologging</loggingtype>
</exception>
As you can see, we are making it fairly explicit that for this exception, the message code to be used is messagecode.employeeconfirmation. The real message can then be extracted from a ResourceBundle for internationalization purposes. We are also making it very clear that we don't need to perform logging for this type of exception, as it's just a confirmation message and not an application error.
Let's look at an example of a context-sensitive exception:
<exception name="RecordNotFoundException">
    <messagecode>messagecode.recordnotfound</messagecode>
    <confirmationind>false</confirmationind>
    <contextind>true</contextind>
    <loggingtype>error</loggingtype>
</exception>
Here, contextind is true for this exception. The context you passed in handleException method can be used to make a unique error code. For instance, if we passed order.getOrder as a context, the resultant message code will be a concatenation of the exception's message code and the context passed. Hence, we get a unique message code like messagecode.recordnotfound.order.getOrder.
 
The data coming from exceptioninfo.xml for each exception can be encapsulated into a data transfer object (DTO) named ExceptionInfoDTO. Now we also need a placeholder where we could cache these objects, as we wouldn't want to parse the XML file again and again and create objects each time an exception occurs. This work can be delegated to a class named ExceptionInfoCache, which will cache all ExceptionInfoDTO objects after reading their information from exceptioninfo.xml.
What's this fuss all about, huh? The core of all this is the ExceptionHandler implementation, which will use data encapsulated in ExceptionInfoDTO for getting the message code, creating ExceptionDTO objects, and then logging it based on the type of logging specified in ExceptionInfoDTO for a given exception.
Here is the handleException method of an ExceptionHandler implementation.
public ExceptionDTO handleException(String userId,
      BaseAppException exp) { 
    ExceptionDTO exDTO = new ExceptionDTO();
    ExceptionInfoCache ecache = 
        ExceptionInfoCache.getInstance();
    ExceptionInfo exInfo = ecache
      .getExceptionInfo(
        ExceptionHelper.getClassName(exp));
    String loggingType = null;
    if (exInfo != null) {
        loggingType = exInfo.getLoggingType();
        exDTO.setConfirmation(exInfo
            .isConfirmation());
        exDTO.setMessageCode(exInfo
            .getMessageCode());
    }
 
    FileLogger logger = new FileLoggerFactory()
        .create();
    logger.logException(exp, loggingType);
Depending upon business requirements, there can be multiple implementations of the ExceptionHandler interface. Deciding which implementation to use can be delegated to a Factory, specifically a ExceptionHandlerFactory class.
Conclusion
Without a comprehensive exception-handing strategy, an ad hoc collection of exception-handling blocks can lead to non-standard error handling and non-maintainable code. Using the above approach, exception handling can be streamlined in a J2EE application.
Resources
  • Sample code for this article
  • Design Patterns: Source of the Facade and Template Method patterns
ShriKant Vashishtha currently works as a solution architect for Tata Consultancy Services Limited (TCS), India.

Return to ONJava.com.
Copyright © 2005 O'Reilly Media, Inc.

 

原创粉丝点击