用EJB 和 Structs处理Web 应用程序中的异常

另一篇 :http://www.javaeye.com/topic/72170 

 

关于Java异常处理的各个方面,已经有很多这样的文章了。一个典型的Web应用程序带有EJBWeb 框架,比如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系统,我们定义两种异常来描述上面的两类错误,即ApplicationExceptionSystemException。当抛出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。所有异常都是从ApplicationExceptionSystemException这两种异常扩展来的因此所有的异常均能在此捕获到。


    如果要处理ApplicationExceptionSystemException的特殊子异常(child exception,那么该如何做呢?只要在action bean中捕获它们,然后以特殊的方式来处理即可。这就是层次结构的优势。因为它们不重要,所以只捕获这两个第二层的异常即可,对于基类action,可以完全忽略它。

5.业务委托抛出什么异常

业务委托层解除客户端层和后端层实现(如在本示例中是EJB系统)之间的耦合。根据该原则,该层也会截获由EJB系统抛出的所有异常。对于EJB的应用程序异常,业务委托层创建了一个一对一的映射,如下表所示。例如,业务委托层捕获到一个EJB CreateException并作为EBJCreateExceptionEJBCreateException重新抛出,名字可任意定义。所有这些业务委托异常也都是从ApplicationException扩展来的。(您想让它们从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检查子系统异常由诸如JDBCJNDI之类的子系统抛出。在此我们不讨论运行时异常runtime Runtime Exceptionexception

应用程序异常

应用程序异常包括标准的EJB应用程序异常和用户自定义的应用程序异常。标准的EJB应用程序异常包括CreateExceptionFinderExceptionObjectNotFoundExceptionDuplicateKeyExceptionRemoveException。这些异常是从EJB的标准方法中抛出,表示出现了业务逻辑错误,例如,CreateException就是由ejbCreat()方法抛出的。用户自定义的异常是用户应用程序定义的针对特定业务问题的异常,例如,InvalidAccountException。当Enterprise bean抛出一个应用程序异常时,容器并不将其封装到另一个异常中。客户端应能处理收到的任何应用程序异常。

EJB1.1子系统异常

由其它子系统抛出的checked异常必须封装到EJBException并从该方法中重新抛出,例如,JDBC SQLException。从其它子系统抛出的类似异常还有JNDIJMSJavaMail,它们都是采用类似的处理方法。EJBExceptionRuntimeException的一个子类型,没必要在方法的抛出语句中声明。容器将EJBException封装RemoteException,并将其反馈给客户端。

EJB 1.0子系统异常

由其它子系统抛出的Checked异常必须封装到RemoteException,并从该方法中重新抛出。

事务异常

如果事务内部出现了系统异常,则EJB容器会回滚该事务。但是,如果在事务中抛出了应用程序异常,则容器并不回滚该事务。这一点,在从EJB中抛出异常时要牢记。

 

如上所述,所有的EJB应用程序异常都会被捕获并作为ApplicationException重新抛出,而EJB系统异常会被捕获并作为SystemException业务委托层中重新抛出。

EJB中重新抛出EJB标准的应用程序异常

有时候,标准EJB 应用程序异常需封装到EJB中。例如,在session bean内访问entity bean之时。一般情况下,标准的应用程序异常都应作为RemoteExceptionsession 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.035.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程序员和网络组件开发员。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值