CSDN博客

img jery_lee

实用EJB开发技巧

发表于2004/10/27 22:51:00  995人阅读

分类: J2EE

http://www0.ccidnet.com/tech/guide/2001/06/29/58_2484.html

一、概述

JavaSoft定义的EJB规范为Java开发者提供了一个创建分布式业务组件的基础。EJB是一种实现业务逻辑(Business Logic)的Java组件,而且它遵从EJB规范所定义的约束。EJB组件生存在EJB容器之内,EJB容器提供了一系列的标准服务,包括事务、持久性、安全、并发等,它使得应用程序开发者无需从头开发这些服务。

在一个由J2EE应用服务器支持的企业级分布式应用中,要想尽量发挥EJB的作用,程序员应该:

  • 严格遵从EJB规范。
  • 使用各种可用的工具辅助Bean开发和兼容性测试。
  • 从其他程序员的经验获益。

EJB 2.0中有四种类型的EJB组件:

  • 无状态会话Bean:提供一种服务,但不保存多个方法调用之间的会话状态信息。
  • 有状态会话Bean:维持会话状态;每一个实例关联到一个特定的用户。
  • 实体Bean:代表着永久性数据的一个对象化描述,常常是数据库中的行。它们拥有作为唯一标识符的主键。实体Bean有两种运作方式:容器管理的持久化(container-managed persistence,CMP),Bean管理的持久化(bean-managed persistence,BMP)。
  • 消息驱动的Bean:这是EJB 2.0新增的类型。消息驱动的Bean实现JMS(Java Message Service)和EJB之间的整合,用来在服务器内执行异步操作。

为了帮助你开发J2EE应用服务器(比如BEA WebLogic Server)支持的企业级分布式应用的EJB,下面精心选择了一些EJB开发的经验和技巧,各个要点的前面以“▲”符号特别注明。本文假定读者是中级以上的Java程序员,而且你已经熟悉如何为应用服务器编写EJB。我们的讨论将从以下几个方面进行:

  • 事务
  • EJB安全
  • 创建主键类
  • 何时避免使用有状态会话Bean
  • 编写业务接口
  • 在事务中使用消息驱动的Bean

二、事务处理技巧

几乎所有的EJB应用都会在某些时候用到事务(Transaction)。事务确保了正确性和可靠性,对于电子商务应用来说是必不可少的。然而,误用或者滥用事务会影响性能,甚至可能产生不正确的结果。会话Bean本身不能事务化,理解这一点非常重要。

一个常见的误解是:当事务被异常中止时,会话Bean的成员变量也会被回退。事实恰好相反,会话Bean只不过是把事务“传播”给所有它们获得的资源。例如,假设事务开始时,会话Bean有一个值为0的成员变量。在事务处理期间,这个成员变量被设置成了2,同时有一个记录被插入到了数据库。如果事务被回退,Bean的成员变量不会恢复0值,但数据库中的记录将不再存在。数据库是一种事务化的资源,它会加入到会话Bean的事务,一旦事务回退,则所有数据库相关的操作也被回退。

  ▲ 会话Bean本身的状态不是事务化的。

2.1 用户交互与事务的性能

如果事务的操作过于复杂、繁多,它们可能导致性能问题。特别地,事务永远不应该包含用户输入操作或其他需要用户参与才能完成的操作。例如,如果用户启动了事务之后,接着又去吃午餐或者去访问另外一个Web网站,则该事务不会被提交;相反,它将继续锁定和占用宝贵的服务器资源。

  一般地,所有的事务分界应该出现在服务器上。有许多众所周知的技术能够避免长时间事务处于持续打开状态。当你要求用户提交、验证表单时,应该把操作分成两个事务:Web页面应该在单一完整的事务之内读取数据,该事务在表单返回给用户之前完成提交;然后由用户根据需要修改数据,表单的更新则在一个新的事务中完成。

  ▲ 事务处理过程中永远不应该插入用户输入或者其他需要用户参与的操作。

2.2 实体Bean:选择事务管理方式

EJB规范允许会话Bean或者选择容器管理的事务,或者选择Bean管理的事务。前者是由EJB容器管理事务,后者是由Bean本身管理事务。对于容器管理的事务,Bean开发者在部署描述器中声明事务属性。在这种情况下,EJB容器会在必要时自动启动和提交事务,Bean开发者无需编写任何管理事务的代码。

对于Bean管理的事务,Bean开发者必须利用事务接口显式地启动和提交事务。Bean开发者的第一选择永远应该是容器管理的事务。如果采用Bean管理的事务,Bean开发者必须确保事务被提交或者回退。虽然象BEA WebLogic Server之类的系统支持事务超时,但Bean开发者不应该依赖系统提供的这种功能,而是应该尽可能早地释放事务资源。如果采用容器管理的事务,这一切都由EJB容器自动处理。

  ▲ 尽量选用容器管理的事务,而不是Bean管理的事务。

三、EJB的安全性

EJB不仅支持一种说明性的安全配置方式,而且还为在Bean代码内部进行安全检查提供了一个简单的可编程接口。在实践中,EJB的安全配置应该置于应用整体安全模型之下考虑。

在基于Web的应用中,验证处理在Web层进行是很常见的情形。在这种环境下,EJB层可能只包含很少的安全约束。这种安排简化了EJB的设计,而且,由于安全检查在表现层进行,我们可以在不修改EJB层的情况下修改应用的安全策略。然而,带有独立可编程客户端的应用常常需要直接访问会话Bean。由于不存在中间层,访问安全必须在EJB层内进行控制。

说明性安全控制是简单应用的首选安全控制方法。由于安全约束在部署描述器中声明,Bean类的业务逻辑之中不需要插入安全检查代码,从而使得业务逻辑代码更加整洁。说明性安全模型以部署描述器中声明的安全角色为基础。如果角色的数量固定不变,而且与客户的数量无关,说明性安全模型最理想。例如,应用可能包含一个用户角色和一个管理员角色。由于这里只有两种访问模式,在部署描述器中声明这些角色是可行的。

然而,当每一个用户要求有各不相同的安全约束时,我们不应该再使用说明性的安全模型,这种应用需要在EJB代码之内用编程方式进行安全检查。另外,结合运用两种安全检查方式的情况也很常见。例如,一个Account(帐户)Bean可以使用说明性的安全检查方式确保只有注册用户才能访问任意一个方法,然后,在Bean的代码之中包含额外的安全约束代码以确保每一个用户只能访问他自己的帐户信息。

  ▲ 当应用程序只包含少量的角色时,使用说明性安全检查模式;当每一个用户需要单独进行安全检查时,使用编程方式进行安全检查。

四、如何为实体Bean编写主键类

无论在永久性存储还是EJB容器之内,EJB主键类都起着唯一标识符的作用。主键类的域常常直接映射到数据库的主键字段。如果主键只是一个单一的实体Bean域,而且它属于Java简单数据类型(比如java.lang.String),那么,Bean开发者无需编写定制的主键类。相反,Bean开发者只需在部署描述器中指定类的名字和主键域的名字。

如果主键映射到一个用户定义的类型或者多个域,Bean开发者必须编写定制的主键类。这个主键类必须实现java.io.Serializable,并包含各个主键域。对于CMP实体Bean,域名字必须匹配Bean类中相应主键域的名字,这将使EJB容器能够把恰当的CMP域赋值给主键类中相应的域。例如,我们可以把雇员的主键定义为一个复合键,这个复合键由userName、deptNumber和officeNumber构成,主键类的代码清单如下:

public final class EmployeePK implements java.io.Serializable {
  public String deptNumber;
  public String userName;
  public int officeNumber;

  private int hash = -1;

  public EmployeePK() {}

  public int hashCode() {
    if (hash == -1) {
      hash = deptNumber.hashCode() ^ userName.hashCode() ^ officeNumber;
    }
    return hash;
   }
   public boolean equals(Object o) {
    if (o == this) return true;
    if (o instanceof EmployeePK) {
      EmployeePK other = (EmployeePK) o;
      return other.hashCode() == hashCode() &&
        other.officeNumber == officeNumber &&
        other.deptNumber.equals(deptNumber) &&
        other.userName.equals(userName);
   } else {
     return false;
   }
  }
}

主键类包含主键域,主键域必须是public类型;另外,主键类还必须包含一个没有参数的构造函数。主键类必须实现hashCode和equals方法。EJB容器内部要用到大量的数据结构,其中许多都通过主键类索引。因此,对于主键类来说,正确、高效地实现这两个方法是至关重要的。

hashCode方法利用主键域返回一个整数。这个函数的目的是生成一个可用于索引表的整数。主键的hashCode值永远不应该改变。因此,hashCode值应该从不可改变的值构造得到。

实现equals方法可能稍微复杂一点。equals方法的第一行应该参照自身(this)检查作为参数传入的引用,这一步骤检查的是对equals方法的调用是否针对其自身进行。虽然从表面上看起来这有点儿奇怪,但是,当容器检查某个数据结构中是否存在特定的主键对象时,这是一种常见的操作。

接下来,equals方法应该确认作为参数传入的引用属于它自己的类型。如果主键类是final类型,这里只需简单地用instanceof操作符进行检查。如果主键类不是final类型,作为参数传入的引用可能属于主键类的派生类型,此时,equals方法必须使用getClass().equals确保类型的严格匹配。与比较类相比,使用instanceof操作符进行比较的开销更小,因此,最好把主键类定义为final类型。

  ▲ 主键类应该是final类型。

五、什么时候避免使用有状态会话Bean

有状态会话Bean代表着单一客户与单个Bean实例之间一个有状态信息的会话。有状态会话Bean不能在多个用户之间共享。你不应该以有状态会话Bean的形式模拟一个共享的缓冲区或者任何其他共享的资源。如果要让多个客户访问单个EJB实例,请使用实体Bean。

  ▲ 有状态会话Bean不能由多个用户共享。

由于每一个客户必须有它自己的有状态会话Bean实例,Bean实例的数量以及对相关资源的需求将很快增长。如果应用程序允许无状态编程模式,那么,无状态会话Bean比有状态会话Bean有着更好的可伸缩性。

应用程序使用有状态会话Bean实例完毕之后,应该总是调用remove方法。这使得EJB容器能够尽快地释放容器资源。

  ▲ 无状态会话Bean比有状态会话Bean有着更好的可伸缩性。

如果要在Web应用中整合有状态会话Bean,Bean开发者应该谨慎。有状态会话Bean不允许并发方法调用。正如前面所提到的,多个请求可能导致对有状态会话Bean进行并发调用。遗憾的是,这种错误通常在应用处于正常负荷状态下出现,所以测试期间它往往被忽视。由于这个原因,建议只在请求范围之内使用有状态会话Bean;对于那些需要在多个请求之间保存数据的应用,请使用实体Bean或者Servlet会话。

六、编写业务接口

远程接口和EJB类之间的关系常常使许多初学EJB开发的程序员感到困惑不解。远程接口和EJB类之间的关系对于让容器截取所有对EJB的方法调用是必要的。容易令人困惑的地方在于:EJB类实现了远程接口所定义的方法,但EJB类并不实现远程接口本身。事实上,EJB类永远不应该实现远程接口。虽然EJB规范允许EJB类在事实上实现远程接口,但这种做法会导致严重的、难以理解的BUG。让EJB类实现远程接口的问题在于:在这种情况下,EJB类可能被作为参数传递给任何期望远程接口类型为参数的方法。

请记住,远程接口的存在使得容器能够截取方法调用以便提供事务、安全之类必需的服务。如果使用的是Bean类,方法调用将直接对Bean对象进行——从而导致了一种危险的情形,即容器不能截获方法调用,或者在出现错误的时候容器不能介入。如果(按照推荐地那样)EJB类不实现远程接口,这个问题在编译时就会显示出来:当我们把Bean类作为远程接口类型的参数传递时,Java编译器将拒绝编译。

  ▲ 永远不要在EJB类中实现远程接口。WebLogic Server提供了一个EJB顺从性检查器,它能够找出所有已经在远程接口中定义、但EJB类没有实现的方法。

七、事务与消息驱动的Bean

消息驱动的EJB整合EJB和JMS。和其他EJB类型一样,消息驱动的EJB生存在EJB容器之内,而且它也从EJB容器的各种服务受益,比如事务、安全以及并发控制等。然而,消息驱动的EJB不直接与客户交互。相反,消息驱动的EJB是JMS消息监听器。客户把消息发布给JMS目的地,然后,JMS提供者和EJB容器协作,把消息发送给消息驱动的EJB。

与其他EJB一样,消息驱动的EJB可以利用EJB容器的事务服务。但是,由于这些Bean永远不会直接与客户交互,它们永远不会参与到客户的事务之中。

与会话Bean一样,在ejb-jar.xml部署描述器中,消息驱动的EJB可以选择由Bean管理事务还是由容器分界事务。对于后者,EJB可以指定Required或者NotSupported属性(因为不存在客户事务,因此支持其他事务属性也就没有必要)。如果事务的属性是NotSupported,消息驱动的EJB将不参与事务。

如果指定了Required属性,EJB容器自动启动事务。从JMS Queue或Topic收到的消息被包含到该事务之中,然后,消息驱动Bean的onMessage方法在该事务环境内被调用。当onMessage方法返回时,EJB容器提交事务。如果事务中止,JMS消息遗留在JMS目的地并被再次发送给消息驱动的EJB。

对于带有Required事务属性的消息驱动Bean,中止事务时应该小心对待。事务的中止或者是由于它被显式地标记为回退,或者是由于出现了系统异常。这时可能出现一个称为“Poison Message”(毒药消息)的问题。假设消息驱动的EJB从股票交易单队列接收交易单,而且它可能遇到不存在的股票代号。当消息驱动的EJB接收到错误的消息时,底层逻辑会因为股票代号非法而中止事务。一旦JMS在一个新的事务中再次发送该消息,这个过程重复出现。显然,这是我们不希望出现的情形。

要解决这种可能出现的问题,一种好方案是分离应用程序错误和系统错误。应用程序的错误,比如非法的股票代号,应该用向出现错误的JMS目的地发送错误消息的方式处理。采用这种方法之后,事务能够提交,“Poison Message”问题也就远离了系统。系统错误可能是后端数据库故障。在这种情况下,事务应该回退。这样,当数据库恢复运行时,消息仍旧在队列中。

阅读全文
0 0

相关文章推荐

img
取 消
img