您的位置:首页 > 移动开发

An Exception Handling Framework for J2EE Applications(onjava)

2006-01-16 11:50 621 查看
 

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
[b]By The O'Reilly Java Authors
[/b]
Table of Contents
Index
Sample Chapter

Read Online--Safari Search this book on Safari:

 
Only This BookAll of 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 h
4000
ide 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.









 











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:














 


 


















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:
 








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:








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.




















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
:





 



































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.















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.


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.





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:






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.
















 




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.
[align=center][/align]
Return to ONJava.com.
Copyright © 2005 O'Reilly Media, Inc.

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐