首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

EJB 异常处理的最佳做法(6)处理系统异常

EJB 异常处理的最佳做法(6)处理系统异常

处理系统异常系统异常处理是比应用程序异常处理更为复杂的论题。由于会话 EJB 组件和实体 EJB 组件处理系统异常的方式相似,所以,对于本部分的所有示例,我们都将着重于实体 EJB 组件,不过请记住,其中的大部分示例也适用于处理会话 EJB 组件。
当引用其它 EJB 远程接口时,实体 EJB 组件会碰到         RemoteException ,而查找其它 EJB 组件时,则会碰到         NamingException ,如果使用 bean 管理的持久性(BMP),则会碰到         SQLException 。与这些类似的受查系统异常应该被捕获并作为         EJBException 或它的一个子类抛出。原始的异常应被包装起来。清单 4 显示了一种处理系统异常的办法,这种办法与处理系统异常的 EJB 容器的行为一致。通过包装原始的异常并在实体 EJB 组件中将它重新抛出,您就确保了能够在想记录它的时候访问该异常。      
清单 4. 处理系统异常的一种常见方式
1
2
3
4
5
6
7
8
9
10
try {
    OrderHome orderHome = EJBHomeFactory.getInstance().getOrderHome();
    Order order = orderHome.findByPrimaryKey(Integer id);
} catch (NamingException ne) {
    throw new EJBException(ne);
} catch (SQLException se) {
    throw new EJBException(se);
} catch (RemoteException re) {
    throw new EJBException(re);
}




避免重复记录通常,异常记录发生在会话 EJB 组件中。但如果直接从 EJB 层外部访问实体 EJB 组件,又会怎么样呢?要是这样,您就不得不在实体 EJB 组件中记录异常并抛出它。这里的问题是,调用者没办法知道异常是否已经被记录,因而很可能再次记录它,从而导致重复记录。更重要的是,调用者没办法访问初始记录时所生成的唯一的标识。任何没有交叉引用机制的记录都是毫无用处的。
请考虑这种最糟糕的情形:单机 Java 应用程序访问了实体 EJB 组件中的一个方法         foo() 。在一个名为         bar() 的会话 EJB 方法中也访问了同一个方法。一个 Web 层客户机调用会话 EJB 组件的方法         bar() 并也记录了该异常。如果当从 Web 层调用会话 EJB 方法         bar() 时在实体 EJB 方法         foo() 中发生了一个异常,则该异常将被记录到三个地方:先是在实体 EJB 组件,然后是在会话 EJB 组件,最后是在 Web 层。而且,没有一个堆栈跟踪可以被交叉引用!      
幸运的是,解决这些问题用常规办法就可以很容易地做到。您所需要的只是一种机制,使调用者能够:
  • 访问唯一的标识
  • 查明异常是否已经被记录了
您可以派生         EJBException 的子类来存储这样的信息。清单 5 显示了         LoggableEJBException 子类:      
清单 5. LoggableEJBException ― EJBException 的一个子类
1
2
3
4
5
6
7
8
9
10
11
public class LoggableEJBException extends EJBException {
    protected boolean isLogged;
    protected String uniqueID;
    public LoggableEJBException(Exception exc) {
    super(exc);
    isLogged = false;
    uniqueID = ExceptionIDGenerator.getExceptionID();
    }
    ..
    ..
}




类         LoggableEJBException 有一个指示符标志(         isLogged ),用于检查异常是否已经被记录了。每当捕获一个         LoggableEJBException 时,看一下该异常是否已经被记录了(         isLogged == false )。如果 isLogged 为 false,则记录该异常并把标志设置为         true 。      
ExceptionIDGenerator 类用当前时间和机器的主机名为异常生成唯一的标识。如果您喜欢,也可以用有想象力的算法来生成这个唯一的标识。如果您在实体 EJB 组件中记录了异常,则这个异常将不会在别的地方被记录。如果您没有记录就在实体 EJB 组件中抛出了         LoggableEJBException ,则这个异常将被记录到会话 EJB 组件中,但不记录到 Web 层中。      
单 6 显示了使用这一技术重写后的清单 4。您还可以继承         LoggableException 以适合于您的需要(通过给异常指定错误代码等)。      
清单 6. 使用 LoggableEJBException 的异常处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
    OrderHome orderHome = EJBHomeFactory.getInstance().getOrderHome();
    Order order = orderHome.findByPrimaryKey(Integer id);
} catch (NamingException ne) {
    throw new LoggableEJBException(ne);
} catch (SQLException se) {
    throw new LoggableEJBException(se);
} catch (RemoteException re) {
    Throwable t = re.detail;
     if (t != null && t instanceof Exception) {
       throw new LoggableEJBException((Exception) re.detail);
     }  else {
       throw new LoggableEJBException(re);
     }
}




记录 RemoteException会话 EJB 组件中的系统异常如果您决定记录会话 EJB 异常,请使用 所示的记录代码;否则,请抛出异常,如 所示。您应该注意到,会话 EJB 组件处理异常可有一种与实体 EJB 组件不同的方式:因为大多数 EJB 系统都只能从 Web 层访问,而且会话 EJB 可以作为 EJB 层的虚包,所以,把会话 EJB 异常的记录推迟到 Web 层实际上是有可能做到的。         

从清单 6 中,您可以看到 naming 和 SQL 异常在被抛出前被包装到了         LoggableEJBException 中。但         RemoteException 是以一种稍有不同 ― 而且要稍微花点气力 ― 的方式处理的。        它之所以不同,是因为在         RemoteException 中,实际的异常将被存储到一个称为         detail (它是         Throwable 类型的)的公共属性中。在大多数情况下,这个公共属性保存有一个异常。如果您调用         RemoteException 的         printStackTrace ,则除打印 detail 的堆栈跟踪之外,它还会打印异常本身的堆栈跟踪。您不需要像这样的         RemoteException 的堆栈跟踪。      
为了把您的应用程序代码从错综复杂的代码(例如         RemoteException 的代码)中分离出来,这些行被重新构造成一个称为         ExceptionLogUtil 的类。有了这个类,您所要做的只是每当需要创建         LoggableEJBException 时调用         ExceptionLogUtil.createLoggableEJBException(e) 。请注意,在清单 6 中,实体 EJB 组件并没有记录异常;不过,即便您决定在实体 EJB 组件中记录异常,这个解决方案仍然行得通。清单 7 显示了实体 EJB 组件中的异常记录:      
清单 7. 实体 EJB 组件中的异常记录
1
2
3
4
5
6
7
8
9
10
11
12
try {
    OrderHome orderHome = EJBHomeFactory.getInstance().getOrderHome();
    Order order = orderHome.findByPrimaryKey(Integer id);
} catch (RemoteException re) {
    LoggableEJBException le =
       ExceptionLogUtil.createLoggableEJBException(re);
    String traceStr = StackTraceUtil.getStackTrace(le);
    Category.getInstance(getClass().getName()).error(le.getUniqueID() +
":" + traceStr);
    le.setLogged(true);
    throw le;
}




您在清单 7 中看到的是一个非常简单明了的异常记录机制。一旦捕获受查系统异常就创建一个新的         LoggableEJBException 。接着,使用类         StackTraceUtil 获取 LoggableEJBException 的堆栈跟踪,把它作为一个字符串。然后,使用 Log4J category 把该字符串作为一个错误加以记录。      
StackTraceUtil 类的工作原理在清单 7 中,您看到了一个新的称为         StackTraceUtil 的类。因为 Log4J 只能记录         String 消息,所以这个类负责解决把堆栈跟踪转换成         String 的问题。清单 8 说明了         StackTraceUtil 类的工作原理:      
清单 8. StackTraceUtil 类
1
2
3
4
5
6
7
8
9
10
public class StackTraceUtil {
public static String getStackTrace(Exception e)
      {
          StringWriter sw = new StringWriter();
          PrintWriter pw = new PrintWriter(sw);
          return sw.toString();
      }
      ..
      ..
}




java.lang.Throwable 中缺省的         printStackTrace() 方法把出错消息记录到         System.err 。         Throwable 还有一个重载的         printStackTrace() 方法,它把出错消息记录到         PrintWriter 或         PrintStream 。上面的         StackTraceUtil 中的方法把         StringWriter 包装到         PrintWriter 中。当         PrintWriter 包含有堆栈跟踪时,它只是调用         StringWriter 的         toString() ,以获取该堆栈跟踪的         String 表示。
返回列表