EJB 规范
Enterprise JavaBean 规范为基于组件的事务性、分布式对象系统定义了一个体系结构。该规范颁布了一个编程模块,即组成 EJB API 的契约或协议以及一组类和接口。EJB 编程模块向 bean 开发人员和 EJB 服务器供应商提供了一组契约,这组契约定义了开发的公共平台。这些契约的目标是在支持一组丰富功能性的同时能够确保供应商之间的可移植性。
EJB 容器
Enterprise Bean 是在称作 EJB 容器的特殊环境中运行的软件组件。容器容纳和管理 Enterprise Bean 的方式与 Java Web 服务器容纳 Servlet 或 HTML 浏览器容纳 Java Applet 的方式相同。Enterprise Bean 不能在 EJB 容器外部运行。EJB 容器在运行时管理 Enterprise Bean 的各个方面,包括远程访问 bean、安全性、持续、事务、并行性和资源的访问与合用。
容器不允许客户机应用程序直接访问 Enterprise Bean。当客户机应用程序调用 Enterprise Bean 上的远程方法时,容器首先拦截调用,以确保持续、事务和安全性都正确应用于客户机对 bean 执行的每一个操作。容器自动为 bean 管理安全性、事务和持续,于是 bean 开发人员不必将这种类型的逻辑写入 bean 代码本身中。Enterprise Bean 开发人员可以将精力集中于封装商业规则,而容器处理其它一切。
如 同 Java Web 服务器管理许多 Servlet,容器同时管理许多 bean。为减少内存消耗和处理,容器合用资源并非常小心地管理所有 bean 的生命周期。当不使用某个 bean 时,容器将它放在池中以便另一个客户机重用,或者可能将它驱逐出内存,仅当需要时再将它调回内存。由于客户机应用程序不能直接访问 bean -- 容器位于客户机和 bean 之间 -- 因此客户机应用程序完全不知道容器的资源管理活动。例如,未在使用的 bean 可能被驱逐出服务器内存,而它在客户机上的远程引用却丝毫不受影响。客户机在远程引用上调用方法时,容器只需重新实例化 bean 就可以处理请求。客户机应用程序并不知道整个过程。
Enterprise Bean 依赖容器来获取它的需求。如果 Enterprise Bean 需要访问 JDBC 连接或另一个 Enterprise Bean,那么它需要利用容器来完成此项操作;如果 Enterprise Bean 需要访问调用者的身份、获取它自身的引用或访问特性,那么它需要利用容器来完成这些操作。Enterprise Bean 通过以下三种机制之一与容器交互:回调方法、EJBContext 接口或 JNDI。
■ 回调方法:
每个 bean 都会实现 EnterpriseBean 接口的子类型,该接口定义了一些方法,称作回调方法。每个回调方法在 bean 的生命周期期间向它提示一个不同事件,当容器要合用某个 bean、将其状态存储到数据库、结束事务、从内存中除去该 bean 等操作时,它将调用这些方法来通知该 bean。回调方法可以让 bean 在事件之前或之后立即执行内部调整。
■ EJBContext:
每个 bean 都会得到一个 EJBContext 对象,它是对容器的直接引用。EJBContext 接口提供了用于与容器交互的方法,因此那个 bean 可以请求关于环境的信息,如其客户机的身份或事务的状态,或者 bean 可以获取它自身的远程引用。
■ Java 命名和目录接口 (JNDI):
JNDI 是 Java 平台的标准扩展,用于访问命名系统,如 LDAP、NetWare、文件系统等。每个 bean 自动拥有对某个特定命名系统(称作环境命名上下文 (ENC))的访问权。ENC 由容器管理,bean 使用 JNDI 来访问 ENC。JNDI ENC 允许 bean 访问资源,如 JDBC 连接、其它 Enterprise Bean,以及特定于该 bean 的属性。
EJB 规范定义了 bean-容器契约,它包括了以上描述的机制(回调、EJBContext、JNDI ENC)以及一组严谨的规则,这些规则描述了 Enterprise Bean 及其容器在运行时的行为、如何检查安全性访问、如何管理事务、如何应用持续,等等。bean-容器契约旨在使 Enterprise Bean 可以在 EJB 容器之间移植,从而可以只开发一次 Enterprise Bean,然后在任何 EJB container 运行该 Enterprise Bean。供应商,如 BEA、IBM 和 GemStone,都销售包含 EJB 容器的应用程序服务器。理想情况下,任何符合规范的 Enterprise Bean 都应该可以在任何符合规范的 EJB 容器中运行。
可 移植性是 EJB 带来的主要价值。可移植性确保了为一个容器开发的 bean 可以迁移到另一个容器,如果另一个容器提供了更好的性能、特性或节省。可移植性还意味着可以跨几个 EJB 容器品牌利用 bean 开发人员的技能,从而向公司和开发人员提供了更好的机会。
除了可移植性,EJB 编程模块的简易性也使 EJB 变得更有价值。由于容器负责管理复杂任务,如安全性、事务、持续、并行性和资源管理,因此 bean 开发人员可以自由地将精力集中在商业规则和一种非常简单的编程模型上。简单的编程模块意味着可以在分布式对象、事务和其它企业系统中更快地开发 bean,而无需高深的知识。EJB 将事务处理和分布式对象开发带入主流。
Enterprise Bean(企业Bean)
为 创建 EJB 服务器端组件,Enterprise Bean 开发人员提供了两个定义 bean 商业方法的接口,以及真正的 bean 实现类。然后客户机使用 bean 的公共接口来创建、操作 bean,以及从服务器除去 bean。实现类,将被称作 bean 类,在运行时被实例化,且成为分布式对象。
Enterprise Bean 存活在 EJB 容器中,客户机应用程序通过网络利用其远程和本地接口访问它们。远程和本地接口暴露了 bean 的能力,并提供了创建、更新、交互和删除 bean 所需的全部方法。bean 是一个服务器端组件,它表示一个商业概念,如 Customer 或 HotelClerk。
远程和本地接口
远程和本地接口表示 bean,但容器不允许从客户机应用程序直接访问 bean。每次请求、创建、或删除 bean 时,容器都会管理整个过程。
本 地接口表示组件的生命周期方法(创建、破坏、查找),而远程接口表示 bean 的商业方法。远程和本地接口分别扩展 javax.ejb.EJBObject 和 javax.ejb.EJBHome 接口。这些 EJB 接口类型定义了一组标准的实用程序方法,并为所有远程和本地接口提供了常用基本类型。
客 户机使用 bean 的本地接口来获取对 bean 的远程接口的引用。远程接口定义了诸如用于更改客户名称的读方法和写方法的商业方法,或用于执行任务的商业方法,如使用 HotelClerk bean 在旅馆预定房间。以下是如何从客户机应用程序访问 Customer bean 的示例。在这种情况下,本地接口是 CustomerHome 类型,而远程接口是 Customer 类型。
CustomerHome home = // ... obtain a reference that implements the home interface.
// Use the home interface to create a new instance of the Customer bean.
Customer customer = home.create(customerID);
// using a business method on the Customer.
customer.setName(someName);
远 程接口定义了 bean 的商业方法,这些方法特定于 bean 表示的商业概念。远程接口是从javax.ejb.EJBObject 接口划分出的子类,而这个接口又是 java.rmi.Remote 接口的子类。以下是 Customer bean 的远程接口的定义:
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Customer extends EJBObject {
public Name getName() throws RemoteException;
public void setName(Name name) throws RemoteException;
public Address getAddress() throws RemoteException;
public void setAddress(Address address) throws RemoteException;
}
远程接口定义了读方法和写方法,用于读取和更新有关商业概念的信息。这代表一种 bean,叫作实体 Bean,它表示持久商业对象(数据存储在数据库中的商业对象)。实体 Bean 表示数据库中的商业数据,并添加特定于该数据的行为。
商业方法
商 业方法还可以表示 bean 执行的任务。虽然实体 Bean 通常都会有面向任务的方法,但是任务通常代表另一种 bean,称作会话 Bean。会话 Bean 不表示类似于实体 Bean 的数据。它们表示执行服务(如在旅馆预定房间)的商业进程或代理程序。以下是 HotelClerk bean 的远程接口的定义,这个 bean 是一种会话 Bean:
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface HotelClerk extends EJBObject {
public void reserveRoom(Customer cust, RoomInfo ri,
Date from, Date to)
throws RemoteException;
public RoomInfo availableRooms(Location loc, Date from, Date to)
throws RemoteException;
}
HotelClerk 远程接口中定义的商业方法表示进程而不是简单的读方法。HotelClerk bean 充当代理程序,因为它代表用户执行任务,但它自己在数据库中并不是持久的。您不需要有关 HotelClerk 的信息;您需要旅馆店员为您执行任务。这是会话 Bean 的典型行为。
有两种基本的 Enterprise Bean:实体 Bean,表示数据库中的数据,以及会话 Bean,表示进程或充当执行任务的代理程序。在构建 EJB 应用程序时,您会创建许多 Enterprise Bean,每一个都表示不同的商业概念。每个商业概念都将由实体 Bean 或会话 Bean 表示。您将根据如何使用商业概念来选择它将成为哪种 bean。
实体 Bean
对于每一个远程接口都有一个实现类,在远程接口中定义了真正实现商业方法的商业对象。这就是 bean 类;bean 的关键元素。以下是 Customer bean 类的部分定义:
import javax.ejb.EntityBean;
public class CustomerBean implements EntityBean {
Address myAddress;
Name myName;
CreditCard myCreditCard;
public Name getName() {
return myName;
}
public void setName(Name name) {
myName = name;
}
public Address getAddress() {
return myAddress;
}
public void setAddress(Address address) {
myAddress = address;
}
...
}
CustomerBean 是实现类。它存储数据并提供读方法和其它商业方法。作为实体 Bean,CustomerBean 提供了客户数据的对象视图。应用程序可以只使用 Customer bean 的远程接口来访问客户数据,而不必在应用程序中编写数据库访问逻辑。实体 Bean 实现了 javax.ejb.EntityBean 类型,它定义了一组 bean 用于与其容器交互的通知方法。
会话 Bean
HotelClerk bean 是一个会话 Bean,它在许多方面都类似于实体 Bean。会话 Bean 表示一组进程或任务,它将代表客户机应用程序执行这些进程或任务。会话 Bean 可以使用其它 bean 来执行任务或直接访问数据库。一小段代码就可以显示会话 Bean 能完成这两件事。下面显示的 reserveRoom() 方法使用其它几个 bean 来完成一个任务,而 availableRooms() 方法使用 JDBC 来直接访问数据库:
import javax.ejb.SessionBean;
public class HotelClerkBean implements SessionBean {
public void reserveRoom(Customer cust, RoomInfo ri,Date from, Date to) {
CreditCard card = cust.getCreditCard();
RoomHome roomHome = // ... get home reference
Room room = roomHome.findByPrimaryKey(ri.getID());
double amount = room.getPrice(from,to);
CreditServiceHome creditHome = // ... get home reference
CreditService creditAgent = creditHome.create();
creditAgent.verify(card, amount);
ReservationHome resHome = // ... get home reference
Reservation reservation = resHome.create(cust,room,from,to);
}
public RoomInfo[] availableRooms(Location loc,Date from, Date to) {
// Make an SQL call to find available rooms
Connection con = // ... get database connection
Statement stmt = con.createStatement();
ResultSet results = stmt.executeQuery("SELECT ...");
...
return roomInfoArray;
}
}
您 可能已经注意到以上定义的 bean 类没有实现远程或本地接口。EJB 并不需要 bean 类来实现这些接口;实际上并不鼓励这种做法,因为远程和本地接口的基本类型(EJBObject 和 EJBHome)定义了许多由容器自动实现的其它方法。然而,bean 类确实提供了在远程接口和方法中定义的所有商业方法的实现。
生命周期方法
除 了远程接口,所有 bean 都还有一个本地接口。本地接口提供了用于创建、破坏和定位 bean 的生命周期方法。这些生命周期行为独立于远程接口,因为它们表示不特定于单个 bean 实例的行为。以下是 Customer bean 的本地接口的定义。请注意,它扩展了 javax.ejb.EJBHome 接口,而该接口扩展了 java.rmi.Remote 接口。
import javax.ejb.EJBHome;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import java.rmi.RemoteException;
public interface CustomerHome extends EJBHome {
public Customer create(Integer customerNumber)
throws RemoteException, CreateException;
public Customer findByPrimaryKey(Integer customerNumber)
throws RemoteException, FinderException;
public Enumeration findByZipCode(int zipCode)
throws RemoteException, FinderException;
}
create() 方法用于创建新的实体。这将在数据库中产生新的记录。本地接口可能有许多 create() 方法。每个 create() 的自变量数量和数据类型由 bean 开发人员指定,但返回类型必须是远程接口数据类型。这种情况下,在 CustomerHome 接口上调用 create() 将返回 Customer 的实例。findByPrimaryKey() 和 findByZipCode() 方法用于定位 customer bean 的特定实例。此外,需要多少查找方法就可以定义多少方法。
再谈远程和本地接口
在运行时,应用程序使用远程和本地接口访问 Enterprise Bean。本地接口允许应用程序创建或定位 bean,而远程接口允许应用程序调用 bean 的商业方法,如下所示:
CustomerHome home = // Get a reference to the CustomerHome object
Customer customer = home.create(new Integer(33));
Name name = new Name("Richard", "Wayne", "Monson-Haefel");
customer.setName(name);
Enumeration enumOfCustomers = home.findByZip(55410);
Customer customer2 = home.findByPrimaryKey(new Integer(33));
Name name2 = customer2.getName();
System.out.println(name); // output is "Richard Wayne Monson-Haefel"
javax.ejb.EJBHome 接口还定义了其它 CustomerBean 自动继承的方法,包括一组允许应用程序破坏 bean 实例的 remove() 方法。
Enterprise Bean 作为分布式对象
远 程和本地接口是两种 Java RMI 远程接口。分布式对象使用 java.rmi.Remote 接口来表示不同地址空间(进程或机器)中的 bean。Enterprise Bean 是一种分布式对象。那表示 bean 类被实例化且在容器中存活,但存活在其它地址空间中的应用程序也访问它。
要使一个地址空间中的对象实例在另一个地址空间中可用,需要一些涉 及网络套接字的技巧。要使该技巧生效,应将实例封装在一个称作“框架”(skeleton)的特殊对象中,该对象拥有到另一个叫作“存根”(stub)的 特殊对象的网络连接。存根(stub)实现远程接口,因此它类似于一个商业对象。但存根(stub)不包含商业逻辑;它拥有到框架(skeleton)的 网络套接字连接。每次在存根(stub)的远程接口上调用商业方法时,存根(stub)将网络消息发送到框架(skeleton),告诉它调用了哪个方 法。框架从存根(stub)接收到网络消息时,它标识所调用的方法以及自变量,然后调用真正的实例上的相应方法。实例执行商业方法,并将结果返回给框架 (skeleton),然后框架(skeleton)将结果发送给存根(stub)。以下图表说明了这个概念:
存 根(stub)将结果返回给调用其远程接口方法的应用程序。从使用存根的应用程序的角度来看,存根(stub)就象在本地运行。实际上,存根(stub) 只是个哑网络对象,它将请求通过网络发送给框架(skeleton),然后框架(skeleton)调用真正实例上的方法。实例完成所有工作;存根 (stub)和框架(skeleton)只是通过网络来回传递方法和自变量。
在 EJB 中,由容器实现远程和本地接口的框架(skeleton),而不是 bean 类。这可以确保由客户机应用程序在这些引用类型上调用的每一个方法都先由容器处理,然后再委托给 bean 实例。容器必须拦截这些针对 bean 的请求,这样它可以自动应用持续(实体 Bean)、事务和访问控制。
分布式对象协议定义了在地址空间之间发送的网络消息的格式。分布式对 象协议非常复杂,但幸好,您看不到这一点,因为它会被自动处理。大多数 EJB 服务器都支持 Java 远程方法协议 (JRMP) 或 CORBA 的网际 ORB 间协议 (IIOP)。bean 和应用程序员只看到 bean 类和远程接口;网络通信的详细信息是隐藏的。
至于 EJB API,程序员不必关心 EJB 服务器使用 JRMP 还是 IIOP -- 使用同一个 API。EJB 规范要求在远程使用 bean 时,应使用特殊版本的 Java RMI API。Java RMI 是用于处理分布式对象的 API,它独立于协议 -- 同样,JDBC 独立于数据库。所以,EJB 服务器可以支持 JRMP 或 IIOP,但 bean 和应用开发人员始终使用同一个 Java RMI API。为了使 EJB 服务器可以支持 IIOP,因此开发了一个特殊版本的 Java RMI,叫作 Java RMI-IIOP。Java RMI-IIOP 使用 IIOP 作为协议,它还使用 Java RMI API。EJB 服务器不必使用 IIOP,但却必须遵守 Java RMI-IIOP 限制,因此 EJB 1.1 使用特殊的 Java RMI-IIOP 约定和类型,但可以使用任何基本协议。