http://dev2dev.bea.com.cn/techdoc/200404159.html
关于Java异常处理的各个方面,已经有很多这样的文章了。一个典型的Web应用程序带有EJB和Web 框架,比如Apache Struts,这篇文章介绍一种具体的点对点策略来设计和进行典型Web应用程序中的异常处理。
1. 典型web应用程序的体系结构
我们以典型的J2EE体系结构作为web应用程序的例子。Entity Beans 用来描述应用程序中的域对象(domain object),Session Beans 用来实现特定于应用程序的工作流。加入业务委托层(Business Delegate tier)是为了隐藏后端EJB系统的实现细节。客户端应该经常通过业务委托层访问后端服务,而不允许直接访问EJB层。现在Apache Struts 在J2EE开发中应用非常广泛,我们还是将它作为web 框架。
简单结构用在很多小型和中型web应用程序中,效果很好。来自不同层的异常都必须经过妥善处理并最后传播到Struts 层,这些异常还要对终端用户显示有用的错误信息。
2. 基异常
在web应用程序中您要定义异常,基异常(Base exception)是所有异常的基类。基异常提供一些基本服务,比如,链式异常(chained exception)(参见参考资料),以及一些方法来打印异常消息和堆栈跟踪。这里有一个关于BaseException的很好例子,摘自Humphrey Sheil的 “Frameworks Save the Day” 。(参见参考资料)
import java.io.PrintStream;
import java.io.PrintWriter;
public class BaseException extends Exception {
/**
* By giving <code>BaseException</code> a reference to a Throwable object,
* exception chaining can be enforced easily.
*/
private Throwable previousThrowable = null;
public BaseException() {
}
public BaseException(String inMessage) {
super(inMessage);
}
public BaseException(String inMessage, Throwable inThrowable) {
super(inMessage);
this.previousThrowable = inThrowable;
}
public BaseException(Throwable inThrowable) {
this.previousThrowable = inThrowable;
}
public void printStackTrace() {
super.printStackTrace();
if (this.previousThrowable != null) {
this.previousThrowable.printStackTrace();
}
}
public void printStackTrace(PrintStream inPrintStream) {
super.printStackTrace(inPrintStream);
if (this.previousThrowable != null) {
this.previousThrowable.printStackTrace(inPrintStream);
}
}
public void printStackTrace(PrintWriter inPrintWriter) {
super.printStackTrace(inPrintWriter);
if (this.previousThrowable != null) {
this.previousThrowable.printStackTrace(inPrintWriter);
}
}
}
|
3.web层如何处理异常
常规的web应用程序有两类典型的错误,应用程序需要不同的方法来处理这两类错误。第一类是应用程序逻辑错误,比如,数据有效性错误和数据格式错误,这类错误一般都是可恢复的,应用程序应该重新显示输入页,允许用户更正错误,并重新运行程序。第二类错误是比较严重的,一般都是与低层系统问题相关。应用程序不能恢复,而且通常情况下,唯一能做的就是,只给终端用户显示一个系统错误页。
通过借鉴EJB系统,我们定义两种异常来描述上面的两类错误,即ApplicationException和SystemException。这两种异常都是从基异常BaseException扩展而来的,是根基异常(即BaseException)下面的第二层异常。这里,清晰的层次是很重要的,我们将在下一节阐述。
很显然,对于业务委托层的客户端,比如Struts,当收到这些异常时,要遵从上面的处理原则。也就是,捕获到ApplicationException时,要重新显示输入页;捕获到 SystemException时,要显示系统错误页。
/**
* This exception indicates an application error the end user may be able to
* recover from. The web tier should generally return control back to the
* input page and display a user friendly message to the end user.
*/
public class ApplicationException extends BaseException {
public ApplicationException(String message, Throwable throwable) {
super(message, throwable);
}
}
/**
* This exception indicates some low-level exceptions from the system which
* normally the application is not able to recover from such as a
RemoteException.
* The web tier should display system error page when catch this exception.
*/
public class SystemException extends BaseException {
public SystemException(String inMessage, Throwable inThrowable) {
super(inMessage, inThrowable);
}
}
|
4.Struts 中的异常处理
Struts 1.1有两种异常处理方法,也就是上面讲的,程序性的和声明性的。在这篇文章里,我们只讲程序性的处理,这也是迄今为止发行的最新版本Struts 1.02所提供的唯一方法,然而这个概念也同样适合声明性的。
在一个基于Structs的设计良好的web应用程序中,所有的请求都必须转到action bean,连接通过action bean连接到到后端系统。简单的说,假设要调用action bean 中的业务委托,我们就从DispatchAction 类扩展出action bean,如下例所示:
package helloworld.ejb.clients;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.actions.*;
import helloworld.exception.*;
import helloworld.ejb.clients.*;
public class HelloWorldActionBean extends DispatchAction {
String method = "HelloWorldActionBean::test: ";
// other methods
public ActionForward test(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
ActionErrors errors = new ActionErrors();
try {
HelloWorldBD helloWorldBD = new HelloWorldBD();
helloWorldBD.createHelloWorldEntity(1, "John", "Limon");
System.out.println("Create is OK");
System.out.println(helloWorldBD.sayHello(1));
helloWorldBD.deleteHelloWorldEntity(1);
System.out.println("Delete is OK");
System.out.println("Everything is OK");
return (mapping.findForward("success"));
} catch (ApplicationException e) {
System.out.println(method + "ApplicaitonError " + e);
// user may able to recover the error, forward to input
page
errors.add(
ActionErrors.GLOBAL_ERROR,
new ActionError("error.application.exception"));
saveErrors(request, errors);
return (new ActionForward(mapping.getInput()));
} catch (SystemException e) {
System.out.println(method + "System Exception " + e);
// user can't recover the error, display system error page
// systemerror is a global forward defined in
struts-config.xml
return (mapping.findForward("system-error"));
}
}
}
|
上面的代码中,HelloWorldBD 是HelloWorldSessionBean的业务委托,其只抛出SystemException和 ApplicationException及其派生的异常。下一节讨论业务委托层抛出的异常。
一旦开始编写更多的bean 及其业务委托,您很快就会发现,就捕获这两种异常并转发给适当的页面而言,这部分代码基本相同。最好用 “Pull up Method” 来执行一些重构操作。我们可以为web应用程序创建一个dispatch action基类,并从这个基类扩展所有的dispatch action。下面是关于该基类的例子:
/**
* The perform() method is almost a exact copy of the same method in original
DispatchAction
* only added the handling of our ApplicationException and SystemException
* Creation date: (8/29/02 11:42:26 AM)
* @author: Hu Ji Rong
*/
import java.io.*;
import java.lang.reflect.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.actions.*;
import framework.exception.*;
import framework.logging.*;
public class BaseDispatchAction extends DispatchAction {
/**
* Retrieve an object from the application scope by its name. This is
* a convience method.
*/
protected Object getApplicationObject(String attrName) {
return servlet.getServletContext().getAttribute(attrName);
}
/**
* Retrieve a session object based on the request and the attribute name.
*/
protected Object getSessionObject(HttpServletRequest req, String attrName) {
Object sessionObj = null;
// Don't create a session if one isn't already present
HttpSession session = req.getSession(false);
sessionObj = session.getAttribute(attrName);
return sessionObj;
}
/**
* This method is a copy from DispatchAction source
* only
* Creation date: (8/29/02 3:23:20 PM)
* @param:
* @return:
*/
public ActionForward perform(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// Identify the request parameter containing the method name
String parameter = mapping.getParameter();
if (parameter == null) {
String message = messages.getMessage("dispatch.handler",
mapping.getPath());
servlet.log(message);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
message);
return (null);
}
// Identify the method name to be dispatched to
String name = request.getParameter(parameter);
if (name == null) {
String message =
messages.getMessage("dispatch.parameter", mapping.getPath(),
parameter);
servlet.log(message);
response.sendError(HttpServletResponse.SC_BAD_REQUEST, message);
return (null);
}
// Identify the method object to be dispatched to
Method method = null;
try {
method = getMethod(name);
} catch (NoSuchMethodException e) {
String message =
messages.getMessage("dispatch.method", mapping.getPath(), name);
servlet.log(message);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message);
return (null);
}
// Dispatch to the specified method
ActionForward forward = null;
try {
Object args[] = { mapping, form, request, response };
forward = (ActionForward) method.invoke(this, args);
} catch (ClassCastException e) {
String message =
messages.getMessage("dispatch.return", mapping.getPath(), name);
servlet.log(message);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message);
return (null);
} catch (IllegalAccessException e) {
String message = messages.getMessage("dispatch.error", mapping.getPath(),
name);
servlet.log(message, e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message);
return (null);
} catch (InvocationTargetException e) {
/// ------------- no changes above this line -------------------
// changes by Hu Ji Rong to handle our ApplicationException and
SystemException
if (e.getTargetException() instanceof BaseException) {
forward = processExceptions(request, mapping, e);
}
if (e.getTargetException() instanceof ApplicationException) {
forward = processExceptions(request, mapping, e);
} else if (e.getTargetException() instanceof SystemException) {
// log the system error
LogService.logError("System Error", e);
// Make the exception avaliable to system error page
request.setAttribute("EXCEPTION_KEY", e);
// "system_error" must be defined in mapping
forward = mapping.findForward("systemerror");
} else {
/// ------------- end of change -------------------
// retain the orginal implementation
String message = messages.getMessage("dispatch.error",
mapping.getPath(), name);
servlet.log(message, e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
message);
return (null);
}
}
// Return the returned ActionForward instance
return (forward);
}
protected void processBaseException(ActionErrors errors, Throwable throwable) {
// handle I18N and error code
}
protected ActionForward processExceptions(
HttpServletRequest request,
ActionMapping mapping,
Throwable throwable) {
ActionErrors errors = new ActionErrors();
ActionForward forward = null;
processBaseException(errors, throwable);
// Either return to the input resource or a configured failure forward
if (mapping.getInput() != null) {
forward = new ActionForward(mapping.getInput());
} else if (mapping.findForward("FAILURE_KEY") != null) {
forward = mapping.findForward("FAILURE_KEY");
}
// Tell the Struts framework to save the errors into the request
saveErrors(request, errors);
// Return the ActionForward
return forward;
}
}
|
该这种技术同样适用于常规的action bean,而不是dispatch action bean。所有异常都是从ApplicationException或SystemException这两种异常扩展来的,因此所有的异常均能在此捕获到。
如果要处理ApplicationException或SystemException的特殊子异常(child exception),那么该如何做呢?只要在action bean中捕获它们,然后以特殊的方式来处理即可。这就是层次结构的优势。因为它们不重要,所以只捕获这两个第二层的异常即可,对于基类action,可以完全忽略它。
5.业务委托抛出什么异常
业务委托层解除客户端层和后端层实现(如在本示例中是EJB系统)之间的耦合。根据该原则,该层也会截获由EJB系统抛出的所有异常。对于EJB的应用程序异常,业务委托层创建了一个一对一的映射,如下表所示。例如,业务委托层捕获到一个EJB CreateException并作为重新抛出,名字可任意定义。所有这些业务委托异常也都是从EBJCreateExceptionEJBCreateExceptionApplicationException扩展来的。(您想让它们从SystemException扩展出来吗?没问题,只不过要记住这个事务问题。)
所有的应用程序异常都应被重新抛出,因为这层不了解任何应用程序逻辑。
EJB 业务委托异常
/**
* Maps to CreateException in EJB
*/
public class EJBCreateException extends ApplicationException {
public EJBCreateException(String inMessage, Throwable inThrowable) {
super(inMessage, inThrowable);
}
}
|
6.EJB异常
这里要讨论两种从EJB系统抛出的异常:应用程序异常(Application Exceptionexception),它表明出现业务逻辑错误;系统异常(System Exception),它们是checked检查子系统异常,由诸如JDBC或JNDI之类的子系统抛出。在此,我们不讨论运行时异常(runtime Runtime Exceptionexception)。
应用程序异常
应用程序异常包括标准的EJB应用程序异常和用户自定义的应用程序异常。标准的EJB应用程序异常包括CreateException、FinderException、ObjectNotFoundException、DuplicateKeyException和RemoveException。这些异常是从EJB的标准方法中抛出,表示出现了业务逻辑错误,例如,CreateException就是由ejbCreat()方法抛出的。用户自定义的异常是用户应用程序定义的针对特定业务问题的异常,例如,InvalidAccountException。当Enterprise bean抛出一个应用程序异常时,容器并不将其封装到另一个异常中。客户端应能处理收到的任何应用程序异常。
EJB1.1子系统异常
由其它子系统抛出的checked异常必须封装到EJBException中并从该方法中重新抛出,例如,JDBC SQLException。从其它子系统抛出的类似异常还有JNDI、JMS和JavaMail,它们都是采用类似的处理方法。EJBException是RuntimeException的一个子类型,没必要在方法的抛出语句中声明。容器将EJBException封装到RemoteException,并将其反馈给客户端。
EJB 1.0子系统异常
由其它子系统抛出的Checked异常必须封装到RemoteException,并从该方法中重新抛出。
事务异常
如果事务内部出现了系统异常,则EJB容器会回滚该事务。但是,如果在事务中抛出了应用程序异常,则容器并不回滚该事务。这一点,在从EJB中抛出异常时要牢记。
EJB 应用程序异常
如上所述,所有的EJB应用程序异常都会被捕获并作为ApplicationException重新抛出,而EJB系统异常会被捕获并作为SystemException业务委托层中重新抛出。
在EJB中重新抛出EJB标准的应用程序异常
有时候,标准EJB 应用程序异常需封装到EJB中。例如,在session bean内访问entity bean之时。一般情况下,标准的应用程序异常都应作为RemoteException从session bean中重新抛出。而有的时候,基于业务需要要重新抛出,这时我们要视情况一一对待。
7. 结束语
在这篇文章里,我们定义了典型J2EE web应用程序中的一个清晰的Java 异常层次结构。我们还提出了一种具体的点对点的方法来处理和发送由后端(EJB层)抛到web前端(Structs层)的异常。这一类的所有异常及其关系如下图所示:
ApplicationException和SystemException是两种基本的异常,要求在web前端有不同的处理方法。它们都是从BaseException扩展来的。程序员所有的特殊应用程序异常是由ApplicationException扩展来的。EJB系统的应用程序异常在业务委托层中一对一的映射中封装为EJB Business Delegate Exceptions,而系统异常则封装为SystemExceptions。
文章中所涉及的示例代码都已经在BEA WebLogic Server 6.1(支持1.1版本的EJB)和4.03及5.0EA版本的IBM WebSphere上经过测试。但是,这里所讲的思想和策略并不局限于某些特定的应用服务器及其版本。如果您想要针对1.0版本EJB的源代码请与作者联系。
参考资料:
· 异常实践,第二部分,使用异常链接来保护调试信息
(http://www.javaworld.com/javaworld/jw-09-2000/jw-0929-ejbframe.html)
· JavaWorld9999上由Humphrey Sheil撰写的"Frameworks Save the Day" (http://www.javaworld.com/javaworld/jw-09-2000/jw-0929-ejbframe.html
· 请到下面的网址下载这篇文章中的示例代码:
http://ftpna2.bea.com/pub/downloads/helloworld.zip
| 作者简介 | |
| 胡继荣1999年以来一直从事J2EE开发。他在新加坡Netlife的BEA WebLogic 平台上开发了电子银行和电子贸易系统。去年,他开始在Lido AeroNet从事WebSphere产品系列。他拥有通信工程师的B.E.和信息安全的M.Sc.,同时,他也是一位Sun认证的Java程序员和网络组件开发员。 | |