另一篇 :http://www.javaeye.com/topic/72170
关于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。当抛出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( // perform是Struts 1.0的,since 1.1之后用execute。 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) { // InvocationTargetException 是一种包装由调用方法或构造方法所抛出异常的受查异常。 //InvocationTargetException 是一个校验异常,它将调用方法或构造子所抛出的异常打包,处理。 /// ------------- no changes above this line ------------------- // changes by Hu Ji Rong to handle our ApplicationException and SystemException, //如果是抛出ApplicationException 和BaseException 则重新显示页面(通过mapping.getInput()获得输入页) // 否则是输入页没指定(在stuct-conf.xml的action中指定)或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
扩展出来吗?没问题,只不过要记住这个事务问题。)
所有的应用程序异常都应被重新抛出,因为这层不了解任何应用程序逻辑。
/** * 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应用程序异常都会被捕获并作为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程序员和网络组件开发员。 |