Board logo

标题: EJB 异常处理的最佳做法(6)处理系统异常 [打印本页]

作者: look_wt    时间: 2018-10-15 19:01     标题: 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 表示。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0