[quote]http://onjava.com/lpt/a/6415[/quote]
<!-- ISI_LISTEN_START --><!-- This article is brought to you by Oreilly Net, your source for all Java news. --><!--CS_PAGE_BREAK-->
by ShriKant Vashishtha
01/11/2006 <!-- sidebar begins --><!-- don't move sidebars --><!-- sidebar ends -->
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.
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:
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. <!-- <csperl file="set_book_cover_image"> --><!-- sidebar begins --><!-- sidebar ends -->
BaseAppException
) only and declare in your throws
clauseIn 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:
throws
clause. Only one exception is required in throws
clause. catch
blocks for application exceptions. If we need to deal with them, one catch
block (for BaseAppException
) is sufficient. ExceptionHandler
, which will be discussed later.
<!--CS_PAGE_INDEX--> |
<!--CS_PAGE_INDEX-->
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();
}
}
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.
<!--CS_PAGE_INDEX--> |
<!--CS_PAGE_INDEX-->
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
.
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.
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
.
<!--CS_PAGE_INDEX--> |
<!--CS_PAGE_INDEX-->
BaseBusinessException
is the base class for this category. BaseDBException
is the base class for this category. BaseSecurityException
. BaseConfirmationException
. java.lang.RuntimeException
class. 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
.
<!--CS_PAGE_INDEX--> |
<!--CS_PAGE_INDEX-->
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.
We talked about an abstraction for handling exceptions in the last section. Here is the contract it should satisfy.
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
.
<!--CS_PAGE_INDEX--> |
<!--CS_PAGE_INDEX-->
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.
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.
<!-- ISI_LISTEN_STOP --><!-- path_info: /a/6415 --><!-- table: a - ref_id: /6415 --><!-- table: a - ref_id: /6415 --><!-- table: a - ref_id: /6415 - cs_rid: -->