ejb能调用另一个ejb吗_EJB异常处理的最佳实践

在hello-world场景中,异常处理非常简单。 每当方法中遇到异常时,都将捕获该异常并打印堆栈跟踪或声明该方法以引发异常。 不幸的是,这种方法不足以处理现实世界中出现的异常类型。 在生产系统中,当引发异常时,最终用户很可能无法处理他或她的请求。 发生此类异常时,最终用户通常会期望以下内容:

  • 清除消息,指示已发生错误
  • 他在访问现有的客户支持系统时可以使用的唯一错误号
  • 快速解决问题,并确保他的请求已被处理或将在设定的时间范围内得到处理

理想情况下,企业级系统不仅将为客户提供这些基本服务,而且还将具有一些必要的后端机制。 客户服务团队应该,例如,立即收到错误通知,以便服务代表在客户要求解决之前知道问题所在。 此外,服务代表应该能够交叉引用用户的唯一错误号和生产日志,以快速识别问题-最好是确切的行号或确切的方法。 为了向最终用户和支持团队提供他们所需的工具和服务,在构建系统时,您必须清楚地了解部署后可能出现问题的所有内容。

在本文中,我们将讨论基于EJB的系统中的异常处理。 我们将首先回顾异常处理基础知识,包括日志记录实用程序的使用,然后Swift进入关于EJB技术如何定义和管理不同类型异常的更详细讨论。 从那里开始,我们将使用代码示例来查看一些常见异常处理解决方案的优缺点,并且我将展示我自己的最佳实践,以充分利用EJB异常处理。

请注意,本文假设您熟悉J2EE和EJB技术。 您应该了解实体bean和会话bean之间的区别。 如果您对实体Bean上下文中的Bean管理的持久性(BMP)和容器管理的持久性(CMP)有所了解,这也将有所帮助。 请参阅相关的主题部分,了解更多关于J2EE和EJB技术。

异常处理基础

解决系统错误的第一步是建立一个与生产系统具有相同构建的测试系统,并跟踪导致该异常的所有代码以及代码中的各个分支。 在分布式应用程序中,调试器可能无法运行,因此您很可能会使用System.out.println()方法来跟踪异常。 尽管它们派上用场,但System.out.println昂贵。 它们在磁盘I / O期间同步处理,这显着降低了吞吐量。 默认情况下,堆栈跟踪记录到控制台。 但是在生产系统中浏览控制台以进行异常跟踪是不可行的。 此外,不能保证它们出现在生产系统中,因为系统管理员可以将System.outSystem.err映射到NT上的' '和UNIX上的dev/null 。 此外,如果您将J2EE应用服务器作为NT服务运行,则甚至没有控制台。 即使将控制台日志重定向到输出文件,重启生产J2EE应用服务器时也有可能覆盖该文件。

由于这些原因,不能选择将包含System.out.println的代码投入生产。 在测试过程中使用它们,然后在生产之前将其删除也不是一个很好的解决方案,因为这样做意味着您的生产代码将无法与测试代码相同。 您需要一种以声明方式控制日志记录的机制,以使您的测试代码和生产代码相同,并且以声明方式关闭日志记录时,生产中产生的性能开销最小。

显而易见的解决方案是使用日志记录实用程序。 有了正确的编码约定,日志记录实用程序将非常注意记录任何类型的消息,无论是系统错误还是警告。 因此,在继续之前,我们将讨论日志记录实用程序。

伐木景观:鸟瞰图

每个大型应用程序在开发,测试和生产周期中都使用日志记录实用程序。 当今的伐木业有少数参与者,其中两个是最知名的。 一个就是Log4J,这是来自Jakarta的Apache的开源项目。 另一个是J2SE 1.4附带的最新条目。 我们将使用Log4J来说明本文讨论的最佳实践。 但是,这些最佳实践并不专门与Log4J绑定。

Log4J具有三个主要组件:布局,追加器和类别。 布局表示要记录的消息的格式。 Appender是消息将记录到的物理位置的别名。 类别是命名实体; 您可以将其视为日志记录的句柄。 布局和附加程序在XML配置文件中声明。 每个类别都有其自己的布局和附加程序定义。 当您获得一个类别并登录到该类别时,该消息最终出现在与该类别关联的所有附加程序中,所有这些消息将以XML配置文件中指定的布局格式表示。

Log4J为消息分配四个优先级:它们是ERROR,WARN,INFO和DEBUG。 为了便于讨论,所有异常均以ERROR优先级记录。 在本文中记录异常时,我们将找到获取类别的代码(使用Category.getInstance(String name)方法),然后调用方法category. error() category. error() (对应于优先级为ERROR的消息)。

尽管日志记录实用程序可以帮助我们将消息记录到适当的持久位置,但它们无法解决问题的根源。 他们无法从生产日志中找到单个客户的问题报告; 该功能由您自己来构建到正在开发的系统中。

有关Log4J或J2SE日志记录实用程序的更多信息,请参见“ 相关主题”部分。

例外类别

异常以不同的方式分类。 在这里,我们将讨论如何从EJB角度对它们进行分类。 EJB规范将异常分为三大类:

  • JVM异常JVM抛出这种类型的异常。 OutOfMemoryError是JVM异常的一个常见示例。 关于JVM异常,您无能为力。 它们表示致命的情况。 唯一正常的退出是停止应用程序服务器,可能会增加硬件资源,然后重新启动系统。
  • 应用程序异常 :应用程序异常是由应用程序或第三方库引发的自定义异常。 这些本质上是检查异常。 它们表示未满足业务逻辑中的某些条件。 在这种情况下,EJB方法的调用者可以适当地处理这种情况并采用替代路径。
  • 系统异常 :JVM通常将系统异常作为RuntimeException的子类抛出。 例如,由于代码中的错误,将引发NullPointerExceptionArrayOutOfBoundsException 。 当系统遇到配置错误的资源(例如拼写错误的JNDI查找)时,会发生另一种系统异常。 在这种情况下,它将抛出一个检查异常。 捕获这些已检查的系统异常并将它们作为未检查的异常抛出是很有意义的。 经验法则是,如果您不能对异常采取任何措施,则它是系统异常,应将其作为未经检查的异常抛出。

注意 :一个已检查的异常是Java类,它是java.lang.Exception子类。 通过子类化java.lang.Exception ,您必须在编译时捕获该异常。 相反, 未经检查的异常是java.lang.RuntimeException子类。 子类化java.lang.RuntimeException可确保编译器不会强迫您捕获异常。

EJB容器如何处理异常

EJB容器拦截EJB组件上的每个方法调用。 结果,导致方法调用的每个异常也会被EJB容器拦截。 EJB规范仅处理两种类型的异常:应用程序异常和系统异常。

EJB规范将应用程序异常定义为在远程接口中的方法签名上声明的任何异常( RemoteException除外)。 应用程序异常是业务工作流程中的一种特殊情况。 抛出这种类型的异常时,将为客户端提供一种恢复选项,通常该选项需要以不同的方式处理请求。 但是,这并不意味着在远程接口方法的throws子句中声明的任何未经检查的异常都将被视为应用程序异常。 该规范明确指出,应用程序异常不应扩展RuntimeException或其子类。

当发生应用程序异常时,除非要求容器显式地回退事务,否则将对关联的EJBContext对象调用setRollbackOnly()方法,否则EJB容器不会回滚该事务。 实际上,可以保证将应用程序异常按原样传递给客户端:EJB容器不会以任何方式包装或处理异常。

系统异常定义为已检查的异常或未检查的异常,EJB方法无法从中恢复。 当EJB容器拦截未经检查的异常时,它将回滚事务并进行任何必要的清除。 然后,容器将未经检查的异常包装在RemoteException并将其抛出给客户端。 因此,EJB容器将所有未经检查的系统异常作为RemoteException (或其子类,例如TransactionRolledbackException )呈现给客户端。

在检查到异常的情况下,容器不会自动执行上述整理工作。 要使用EJB容器的内部整理,您必须将已检查的异常作为未检查的异常抛出。 每当发生检查到的系统异常(例如NamingException )时,都应该通过包装原始异常来抛出javax.ejb.EJBException或其子类。 因为EJBException本身是未经检查的异常,所以不需要在方法的throws子句中声明它。 EJB容器捕获EJBException或其子类,将其包装在RemoteException ,然后将RemoteException给客户端。

尽管系统异常是由应用程序服务器记录的(按照EJB规范的要求),但日志格式在一个应用程序服务器与另一个应用程序服务器之间会有所不同。 通常,企业将需要在生成的日志上运行shell / Perl脚本,以便访问所需的统计信息。 为了确保统一的日志记录格式,最好在代码中记录异常。

注意 :EJB 1.0规范要求将检查的系统异常作为RemoteException 。 从EJB 1.1开始,该规范要求EJB实现类不应完全抛出RemoteException

常见的异常处理策略

在没有异常处理策略的情况下,项目团队中的不同开发人员可能会编写代码以不同方式处理异常。 至少,这可能会导致生产支持团队感到困惑,因为在系统的不同区域中可能会描述和处理不同的单个异常。 缺乏策略还会导致在整个系统的多个位置进行日志记录。 日志应集中或分解为多个可管理的单元。 理想情况下,异常日志记录应该在尽可能少的地方进行而不会损害内容。 在本节以及随后的部分中,我将演示可以在整个企业系统中以统一的方式实现的编码策略。 您可以从“ 相关主题”部分下载本文开发的实用程序类。

清单1显示了来自会话EJB组件的方法。 此方法删除特定日期之前客户下的所有订单。 首先,它获得OrderEJB Home Interface。 接下来,它从特定客户那里获取所有订单。 当遇到在特定日期之前下的订单时,它将删除该订单项目,然后删除该订单本身。 请注意,将引发三个异常,并显示了三种常见的异常处理实践。 (为简单起见,假定未使用编译器优化。)

清单1.三种常见的异常处理实践
100  try {
101    OrderHome homeObj = EJBHomeFactory.getInstance().getOrderHome();
102    Collection orderCollection = homeObj.findByCustomerId(id);
103    iterator orderItter = orderCollection.iterator();
104    while (orderIter.hasNext()) {
105      Order orderRemote = (OrderRemote) orderIter.getNext();
106      OrderValue orderVal = orderRemote.getValue();
107      if (orderVal.getDate() < "mm/dd/yyyy") {
108        OrderItemHome itemHome = 
              EJBHomeFactory.getInstance().getItemHome();
109        Collection itemCol = itemHome.findByOrderId(orderId)
110        Iterator itemIter = itemCol.iterator();
111        while (itemIter.hasNext()) {
112          OrderItem item = (OrderItem) itemIter.getNext();
113          item.remove();
114        }
115        orderRemote.remove();
116      }
117    }
118  } catch (NamingException ne) {
119    throw new EJBException("Naming Exception occurred");
120  } catch (FinderException fe) {
121    fe.printStackTrace();
122    throw new EJBException("Finder Exception occurred");
123  } catch (RemoteException re) {
124    re.printStackTrace();
125    //Some code to log the message
126    throw new EJBException(re);
127  }

现在,让我们使用上面的代码插图来查看三种已证明的异常处理实践中的缺陷。

抛出/抛出带有错误消息的异常
NamingException可能发生在101或108行上。当NamingException发生时,此方法的调用者将获取RemoteException并可以将异常回溯到119行。调用者无法确定实际的NamingException发生在101或108行上。除非将异常内容记录下来,否则不会保留,否则问题的根源是无法追踪的。 在这种情况下,据说异常的内容已被“吞噬”。 如该示例所示,使用消息引发或重新引发异常不是一个好的异常处理解决方案。

登录到控制台并引发异常
FinderException可能在第102或109行发生。但是,由于将异常记录到控制台,因此,只有在控制台可用时,调用方才可以追溯到第102或109行。 显然,这是不可行的,因此只能将异常追溯到第122行。此处的推理与上面相同。

包装原始异常以保留内容
RemoteException可能发生在第RemoteException或115行。它被catch在第123行的catch块中。然后将其包装在EJBException ,因此无论调用者记录它什么地方,它都可以保持不变。 尽管比前两个更好,但是这种方法证明了没有日志记录策略。 如果deleteOldOrders()方法的调用者记录了该异常,则将导致重复记录。 而且,尽管有日志记录,但是当客户报告问题时,不能交叉引用生产日志或控制台。

EJB异常处理启发式

EJB组件应该抛出哪些异常,您应该在系统中将它们记录在哪里? 这两个问题错综复杂地联系在一起,应该一起解决。 答案取决于以下因素:

  • 您的EJB系统设计 :在良好的EJB设计中,客户端永远不会在实体EJB组件上调用方法。 大多数实体EJB方法调用发生在会话EJB组件中。 如果您的设计遵循这些原则,则应使用会话EJB组件记录异常。 如果客户端直接调用实体EJB方法,那么您也应该在实体EJB组件中记录消息。 但是,有一个陷阱:会话EJB组件也可以调用相同的实体EJB方法。 在这种情况下如何防止重复记录? 同样,当一个会话EJB组件调用另一个会话EJB组件时,如何防止重复日志记录? 我们将很快探索处理这两种情况的通用解决方案。 (请注意,EJB 1.1在体系结构上不会阻止客户端调用实体EJB组件上的方法。在EJB 2.0中,您可以通过为实体EJB组件定义本地接口来强制执行此限制。)
  • 计划的代码重用程度 :这里的问题是您是打算在多个位置添加日志记录代码还是要重新设计和重构代码以减少日志记录代码。
  • 您要服务的客户端类型 :考虑是否要服务J2EE Web层,独立Java应用程序,PDA或其他客户端,这一点很重要。 Web层设计具有各种形状和大小。 如果使用的是Command模式,其中Web层每次都通过传递不同的命令来调用EJB层中的相同方法,那么将异常记录在发生命令执行的EJB组件中很有用。 在大多数其他Web层设计中,将异常记录在Web层本身中更加容易和更好,因为您需要在更少的位置添加异常记录代码。 如果您将Web层和EJB层并置在一起,并且不需要支持任何其他类型的客户端,则应该考虑使用后一种方法。
  • 您将要处理的异常类型(应用程序或系统) :处理应用程序异常与处理系统异常有很大不同。 系统异常是在EJB开发人员无意的情况下发生的。 由于尚不清楚系统异常的意图,因此内容应指示异常的上下文。 如您所见,最好通过包装原始异常来解决此问题。 另一方面,EJB开发人员通常通过包装消息来显式引发应用程序异常。 由于应用程序异常的意图很明确,因此没有理由保留其上下文。 这种类型的异常无需记录在EJB层或客户端层中; 相反,应该以有意义的方式将其呈现给最终用户,并提供替代的解决方案。 系统异常消息对于最终用户而言并不需要非常有意义。

处理应用程序异常

在本节以及随后的几节中,我们将更仔细地研究针对应用程序异常和系统异常以及Web层设计的EJB异常处理。 作为此讨论的一部分,我们将探索处理会话和实体EJB组件引发的异常的不同方法。

实体EJB组件中的应用程序异常
清单2显示了实体EJB上的ejbCreate()方法。 此方法的调用者传入OrderItemValue并请求创建OrderItem实体。 因为OrderItemValue没有名称,所以将引发CreateException

清单2.实体EJB组件中的示例ejbCreate()方法
public Integer ejbCreate(OrderItemValue value) throws CreateException {
    if (value.getItemName() == null) {
      throw new CreateException("Cannot create Order without a name");
    }
    ..
    ..
    return null;
}

清单2显示了CreateException一种非常典型的用法。 同样,如果方法的输入参数没有正确的值,则finder方法将引发FinderException

但是,如果使用容器管理的持久性(CMP),则开发人员将无法控制finder方法,并且CMP实现可能永远不会引发FinderException 。 尽管如此,最好在Home接口上的finder方法的throws子句中声明FinderExceptionRemoveException是删除实体时引发的另一个应用程序异常。

从实体EJB组件引发的应用程序异常相当限于这三种类型( CreateExceptionFinderExceptionRemoveException )及其子类。 大多数应用程序异常都源自会话EJB组件,因为这是进行智能决策的地方。 实体EJB组件通常是哑类,其唯一职责是创建和获取数据。

会话EJB组件中的应用程序异常
清单3显示了来自会话EJB组件的方法。 此方法的调用方尝试订购n数量的特定类型的物品。 SessionEJB()方法发现库存不足,并抛出NotEnoughStockExceptionNotEnoughStockException适用于特定业务场景; 当引发此异常时,将为呼叫者建议一条替代路线,使他可以订购较少数量的物品。

清单3.会话EJB组件中的示例容器回调方法
public ItemValueObject[] placeOrder(int n, ItemType itemType) throws
NotEnoughStockException {

    //Check Inventory.
    Collection orders = ItemHome.findByItemType(itemType);
    if (orders.size() < n) {
      throw NotEnoughStockException("Insufficient stock for " + itemType);
    }
}

处理系统异常

与应用程序异常处理相比,系统异常处理是一个涉及更多的讨论。 因为会话EJB组件和实体EJB组件以类似的方式处理系统异常,所以在本节中,我们将重点关注实体EJB组件中的示例,但是请记住,大多数示例也可以应用于处理会话EJB组件。

实体EJB组件在引用其他EJB远程接口时遇到RemoteException ,在查找其他EJB组件时遇到NamingException ,而如果使用bean管理的持久性(BMP)则遇到SQLException 。 此类已检查的系统异常应作为EJBException或其子类之一捕获并抛出。 原始异常应该被包装。 清单4显示了一种与系统异常的EJB容器行为一致的处理系统异常的方法。 通过包装原始异常并将其重新放入实体EJB组件中,可以确保在要记录异常时可以访问该异常。

清单4.处理系统异常的常用方法
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组件中并抛出该异常。 这里的问题是,调用者无法知道该异常已被记录,并且可能会再次记录该异常,这将导致重复记录。 更重要的是,调用者无法访问在初始日志记录期间生成的唯一ID。 任何没有交叉引用机制的日志记录都是不好的。

考虑这种最坏的情况:实体EJB组件中的方法foo()由独立的Java应用程序访问。 在会话EJB方法(称为bar()访问相同的方法。 Web层客户端在会话EJB组件上调用方法bar() ,并记录异常。 如果从Web层调用会话EJB方法bar()时,实体EJB方法foo()发生异常,则该异常将被记录在三个位置:首先在实体EJB组件中,然后在会话EJB中组件,最后是Web层。 而且没有堆栈跟踪之一可以交叉引用!

幸运的是,以一般方式解决这些问题相当容易。 您所需要的只是调用者的机制:

  • 访问唯一ID
  • 找出异常是否已被记录

您可以将EJBException子类化以存储此信息。 清单5显示了LoggableEJBException子类:

清单5. LoggableEJBException-EJBException的子类
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 )。 如果为false,则记录异常并将标志设置为true

ExceptionIDGenerator类使用计算机的当前时间和主机名为ExceptionIDGenerator生成唯一的ID。 如果愿意,可以使用特殊算法生成唯一ID。 如果将异常记录在实体EJB组件中,则不会在其他位置记录该异常。 如果将LoggableEJBException扔到实体EJB组件中而不进行日志记录,它将记录在会话EJB组件中,而不记录在Web层中。

清单6显示了使用此技术重写的清单4。 您还可以扩展LoggableException使其适合您的需求(通过将错误代码分配给异常等)。

清单6. LoggableEJBException的异常处理
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

从清单6中可以看到,命名和SQL异常在抛出之前已包装在LoggableEJBException中。 但是, RemoteException的处理方式略有不同-且劳动强度更高。

这是不同的,因为在RemoteException ,实际的异常将存储在名为detail (类型为Throwable )的公共属性中。 在大多数情况下,此公共属性会例外。 如果在RemoteException上调用printStackTrace ,则除了显示详细信息的堆栈跟踪之外,还会打印异常本身的堆栈跟踪。 您不需要这样的RemoteException的堆栈跟踪。

为了将您的应用程序代码与诸如RemoteException类的复杂代码隔离开来,将这些行重构为一个名为ExceptionLogUtil的类。 使用此类,您需要做的就是在需要创建LoggableEJBException时调用ExceptionLogUtil.createLoggableEJBException(e) 。 请注意,实体EJB组件不会记录清单6中的异常。 但是,即使您决定将异常记录在实体EJB组件中,该解决方案仍然有效。 清单7显示了实体EJB组件中的异常日志记录:

清单7.实体EJB组件中的异常日志记录
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类别将字符串记录为错误。

StackTraceUtil类如何工作

在清单7中,您看到了一个名为StackTraceUtil的新类。 由于Log4J只能记录String消息,因此此类解决了将堆栈跟踪转换为String的问题。 清单8展示了StackTraceUtil类的工作方式:

清单8. StackTraceUtil类
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.errThrowable还具有重载的printStackTrace()方法以登录到PrintWriterPrintStreamStackTraceUtil的上述方法将StringWriter包装在PrintWriter 。 当PrintWriter包含堆栈跟踪时,它仅调用StringWriter上的toString()即可获取堆栈跟踪的String表示形式。

Web层的EJB异常处理

在Web层设计中,将异常日志记录机制放在客户端通常更容易,更有效。 为此,Web层必须是EJB层的唯一客户端。 此外,Web层必须基于以下模式或框架之一:

  • 模式 :业务委托,FrontController或拦截过滤器
  • 框架 :Struts或任何类似的包含层次结构的MVC框架

为什么异常日志记录应该在客户端进行? 好吧,首先,控件尚未传递到应用程序服务器之外。 由JSP页面,Servlet或其助手类组成的所谓的客户层在J2EE应用服务器本身上运行。 其次,设计良好的Web层中的类具有层次结构(例如,在Business Delegate类,Intercepting Filter类,http请求处理程序类,JSP基类或Struts Action类中)或单点调用FrontController servlet的形式。 这些层次结构的基类或Controller类的中心点可以包含异常日志记录代码。 对于基于会话EJB的日志记录,EJB组件中的每个方法都必须具有日志记录代码。 随着业务逻辑的增长,会话EJB方法的数量也会随之增加,日志代码的数量也会随之增加。 Web层系统将需要较少的日志记录代码。 如果您将Web层和EJB层并置在一起,并且不需要支持任何其他类型的客户端,则应该考虑使用这种替代方法。 无论如何,日志记录机制不会改变。 您可以使用前面几节中介绍的相同技术。

现实世界的复杂性

到目前为止,您已经看到了用于会话和实体EJB组件的简单方案中的异常处理技术。 但是,应用程序异常的某些组合可能会更加混乱,并易于解释。 清单9显示了一个示例。 OrderEJBejbCreate()方法尝试获取对CustomerEJB的远程引用,这将导致FinderExceptionOrderEJBCustomerEJB都是实体EJB组件。 您应该如何在ejbCreate()解释此FinderException ? 您是否将其视为应用程序异常(因为EJB规范将其定义为标准应用程序异常),还是将其视为系统异常?

清单9. ejbCreate()方法中的FinderException
public Object ejbCreate(OrderValue val) throws CreateException {
     try {
        if (value.getItemName() == null) {
          throw new CreateException("Cannot create Order without a name");
        }
        String custId = val.getCustomerId();
        Customer cust = customerHome.fingByPrimaryKey(custId);
        this.customer = cust;
     } catch (FinderException ne) {
     	  //How do you handle this Exception ?
     } catch (RemoteException re) {
	  //This is clearly a System Exception
	  throw ExceptionLogUtil.createLoggableEJBException(re);
     }
     return null;
}

虽然没有什么可以阻止您将其视为应用程序异常,但最好将FinderException视为系统异常。 原因如下:EJB客户端倾向于将EJB组件视为黑匣子。 如果调用者createOrder()方法得到一个FinderException ,它没有任何意义给调用者。 OrderEJB试图设置客户远程引用的事实对调用者是透明的。 从客户的角度来看,失败只是意味着无法创建订单。

这种情况的另一个示例是会话EJB组件尝试创建另一个会话EJB并获取CreateException 。 类似的情况是实体EJB方法尝试创建会话EJB组件并获取CreateException 。 这两个异常都应视为系统异常。

您可能遇到的另一个挑战是会话EJB组件在其容器回调方法之一中获取FinderException 。 您必须根据具体情况处理这种情况。 您可能决定将FinderException视为应用程序异常或系统异常。 考虑清单1中的情况,其中调用方在会话EJB组件上调用deleteOldOrder方法。 而不是捕获FinderException ,如果我们抛出该FinderException怎么办? 在这种特殊情况下,将FinderException视为系统异常似乎是合乎逻辑的。 The reasoning here is that session EJB components tend to do a lot of work in their methods, because they handle workflow situations and act as a blackbox to the caller.

On the other hand, consider a scenario where a session EJB is handling an order placement. To place an order, the user must have a profile -- but this particular user doesn't have one. The business logic may want the session EJB to explicitly inform the user that her profile is missing. The missing profile will most likely manifest as a javax.ejb.ObjectNotFoundException (a subclass of the FinderException ) in the session EJB component. In this case, the best approach is to catch the ObjectNotFoundException in the session EJB component and throw an application exception, letting the user know that her profile is missing.

Even with good exception handling strategies, there is another problem that often occurs in testing, and more importantly in production. Compiler and runtime optimizations can change the overall structure of a class, which can limit your ability to track down an exception using the stack trace utility. This is where code refactoring comes to your rescue. You should split large method calls into smaller, more manageable chunks. Also, whenever possible, have your exceptions typed as much as needed; whenever you catch an exception, you should be catching a typed exception rather than a catch-all.

结论

We've covered a lot of ground in this article, and you may be left wondering if all the up-front design we've discussed is worth it. It is my experience that even in a small- to medium-sized project, the effort pays for itself in the development cycle, let alone the testing and production cycles. Furthermore, the importance of a good exception-handling architecture cannot be stressed enough in a production system, where downtime can prove devastating to your business.

I hope you will benefit from the best practices demonstrated in this article. To follow up on some of the information presented here, check the listings in the Related topics section.


翻译自: https://www.ibm.com/developerworks/java/library/j-ejbexcept/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值