大家讨论一下EJB和普通的BEAN的使用地方在哪里

icey 2002-02-04 12:18:21
...全文
84 2 打赏 收藏 转发到动态 举报
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
lmh79 2002-02-04
  • 打赏
  • 举报
回复
企业级EJB开发实例
(转载自中国计算机报 文:熊俊、张黎明 2001年10月09日 09:39)

对企业开发人员来讲,难以编写分布式商务应用程序和其它任何较大的应用程序是他们今天所面临的一个共同问题。如果一个应用程序是分布式或在网络中以多重形式出现,那它应该是一个综合化的产物。EJB结构是Java平台上的服务器端组件模型,设计EJB结构的目的是通过使企业开发人员将注意力只集中于编写商务逻辑,从而解决上面所提出的问题。这些企业级bean使应用程序编程人员能够开发与平台无关、面向事务的分布式应用程序,而且这种应用程序可在众多厂商的服务器上运行。

尽管企业级bean减轻了应用程序编程人员处理系统功能的负担,但这些bean相当复杂,开发它们并不是一项简单的工作。以下几个部分将具体分析企业级bean的开发,并会通过一个具体实例来介绍如何通过可视化编程工具Jbuilder在Weblogic应用服务器下开发一个企业级bean。

企业级bean的开发
企业级bean有两种类型:会话bean和实体bean。它们在下列几个方面很相似:必须整合到应用程序中、必须部署到符合EJB规范的容器中,并且必须在符合EJB规范的服务器上运行。

开发企业级bean要完成四项主要的任务:即创建、包装、测试和部署。

要创建企业级bean,你必须遵循Sun公司的EJB规范所定义的一组接口。例如,除了定义一个企业级bean类之外,你还必须为每个企业级bean定义远程接口和本地接口。远程接口定义企业bean业务方法的客户视图,而本地接口定义企业级 bean对象生存周期的客户视图,生存周期包括诸如企业级bean的创建和删除这类事件。

你还必须确保在企业级bean的接口和类中定义的方法保持一致。如果创建的是实体bean,则你必须定义它的持久性域,并将这些持久性域映射到持久数据存储,如关系数据库。

一旦创建了企业级bean,就必须针对特定的bean容器部署它,即生成本地接口和远程接口的实现类。完成部署之后,还需要测试 bean 的远程方法和本地方法。最后,一旦完成测试,你就可以将bean打包,以便将其安装在应用服务器上。

开发过程
下面笔者将具体讲解Jbuilder和Weblogic5.1下的一个EJB开发的具体过程:

环境搭建

安装Weblogic5.1和Jbuilder4.0企业版(从网上下载的Jbuider一般是先安装Foundation版再在此基础上安装企业版)。Weblogic5.1安装好后,如果Weblogic Server无法启动,可能是由WeblogicLicense.xml过期所致,可到网上下载一新版将此文件覆盖即可将Server启动。

环境配置

对于Weblogic5.1,以文本形式打开Weblogic根目录下的startWeblogic这个NT命令文件。对JAVA_CLASSPATH和Weblogic_ CLASSPATH这两个属性值更改如下(由于笔者的数据库服务器是Oracle8i,所以在此引用Oracle的JDBC Driver驱动):

set JAVA_CLASSPATH=%JAVA_HOME%\lib\classes.zip;.\classes\boot;.\eval\cloudscape\lib\cloudscape.jar;D:\Oracle\Ora81\jdbc\lib\classes12.zip
set Weblogic_CLASSPATH=.\license;.\classes;
.\lib\Weblogicaux.jar;.\myserver\serverclasses;D:\Oracle\Ora81\jdbc\lib\classes12.zip;

%JAVA_HOME%是系统变量,指的是JDK的安装目录,可在98或NT环境下对此环境变量进行设置。

然后找到Weblogic安装的根目录下的Weblogic.properties文件,以文本文件形式打开此文件并在此文件中配置Weblogic服务的监听端口(80)和数据源连接池(FordPool),配置如下:

Weblogic.system.listenPort=80
Weblogic.jdbc.connectionPool.FordPool=\
url=jdbc:oracle:thin:@130.140.55.50:1521:xjdb1,\
driver=oracle.jdbc.driver.OracleDriver,\
loginDelaySecs=1,\
shrinkPeriodMins=15,\
refreshMinutes=10,\
testTable=dual,\
props=user=fordxj;password=fordxj;server=serverName
Weblogic.jdbc.TXDataSource.FordPool=FordPool
Weblogic.allow.reserve.Weblogic.jdbc.connectionPool.FordPool=everyone

对数据源连接池主要是url属性和props属性,其中url属性指出了Oracle服务所在的主机名(130.140.55.50)、端口(1521)和数据库服务名(xjdb1);props属性指出了用户名、口令和Oracle服务所在的主机名。

打开Jbuilder4.0点击Tools→Enterprise Setup,接着打开Application Server标签下的Weblogic5.1标签,定位Weblogci5.1的安装目录。

EJB工程开发

先新建一个空的工程,在建此工程的第二步时选择Required libraries选项时选定Weblogic5.1。再在此工程中新建一个Entity EJB Group和一个Enterprise JavaBean。在建Enterprise JavaBean 时提供了四种选项,这四种选项分别对应了EJB的四种类型(Stateful session bean、Stateless session bean、BMP和CMP)。我们选定EJB的类型为Bean managed persistence entity bean(BMP),之后的几步中可以更改JNDI名称。

工程建好后,点击Project→project property,打开Enterprise标签,确保选中的Application server 是Weblogic5.1,再打开Build标签下的Weblogic标签,确保选中Use Weblogic EJBC to generate stub files选项。

编写每一个EJB时,都必须为它定义一个宿主接口、一个远程接口和一个Bean类。

宿主接口定义了Bean的生命周期方法,客户机调用这些方法创建、寻找并删除Bean实例。宿主接口必须继承javax.ejb.EJBHOme类。客户机通过命名和目录接口(JNDI)对宿主接口进行定位,然后再利用这个宿主接口创建一个Bean对象。宿主接口指定了一个或多个create方法;对宿主接口中的create方法来说,必须在Bean类中为它指定一个具有同样签名的ejbCreate方法。客户机调用宿主机上的create方法时,不管当时请求的是什么服务,容器都会调用Bean内的ejbCreate方法。

远程接口列出了客户机可以调用的所有Bean方法或公用接口。远程接口继承了javax.ejb.EJBObject类。远程接口可以定义零个或多个业务方法,这些方法必须声明为“public”,这些方法不能是“final”方法,方法名禁止以“ejb”开头。远程接口中的方法必须是合法的RMI方法:它们的参数和返回值必须是序列化类型。

Bean类用于实现Bean的业务逻辑,Bean类将驻留于服务器上。对宿主接口和远程接口中的每一个方法来说,Bean类中必须有一个能与之相符的方法签名。

由于我们建的是一个BMP,它是直接关联到表中记录,所以对应每一个字段在Bean类都有对应的属性和相应的Set、Get方法。而且在ejbLoad方法中必须对这些属性赋值,在ejbStore方法中将相应的属性值存入到对应的字段中去。

对于我们在Weblogic.properties文件中所配置的数据源连接池,在代码中可以以如下方式引用:

InitialContext ic = new InitialContext();
datasource = (DataSource) ic.lookup(FordPool);
dbConnection = datasource.getConnection();

部署并测试EJB

EJB创建之后,下一步就是针对Weblogic来部署它,也就是生成本地接口和远程接口的实现类(一些以stub或impl结尾的文件)。其实这一步的工作只要在Jbuilder中点击鼠标就能实现。右键点击工程文件→Rebuild,Jbuilder将会生成一个与EJBGroup同名的jar文件。这个jar文件中包括了我们所需的所有实现类文件。

EJB类都实现之后,我们可以新建一个测试类来测试它。点击File→New→EJB Test Client。新建的这个测试类会产生针对EJB类中各方法的测试方法。我们可以在main方法中调用这些方法来测试EJB。

发布EJB

EJB中的各方法都测试通过后,我们可将部署时形成的jar文件发布到Weblogic服务器并在客户机中调用。发布到webLogci中有两种方式:热发布和冷发布。热发布就是在Weblogic.properties文件中加入下面一行:(Infopub_ Group.jar是笔者发布的jar文件)

Weblogic.ejb.deploy=\d:/mywork/InfoPub /Infopub_Group.jar,\

冷发布是在Weblogic服务器已经在运行的情况下,用Weblogic提供的工具EJB deployer来发布的。

通过创建、包装、测试和部署,我们就利用Jbuilder在Weblogic应用服务器下开发出了一个完整的企业级bean。
hui2008 2002-02-04
  • 打赏
  • 举报
回复
全面研读 EJB 2.0
EJB 2.0 中引人注目的变化增强了应用程序开发的灵活性和可移植性

Richard Monson-Haefel
OpenEJB 首席设计师
2000 年 6 月

新的 EJB 2.0 规范不仅仅是一个新的阶段性发行版,它加入了许多引人注目的变动,包括 CMP 组件模型中的一些变动和一种新的 bean 类型,它们将增强您在开发应用程序时的灵活性和可移植性。请率先了解此新规范的功能,本月已发布了它的公开草案。

6 月 2 号发布的 Enterprise JavaBeans 2.0 不仅是一个阶段性发行版,而且是该规范的一个新版本。整个规范有 500 多页,比以前的 EJB 1.1 规范长了 200 页 (66%)。该规范中最重要的变动是对容器管理的持久性 (CMP) 所作的更改,以及引入了一种全新的 bean 类型,即 MessageDrivenBean。

EJB 2.0 中的大量更改都集中在一种新 CMP 组件模型的定义中。它完全不同于旧的 CMP 模型,因为它引入了一个全新的成员,即持久性管理器,并引入了全新的方式来定义容器管理的字段,以及定义这些字段与其它 bean 和从属对象的关系。

MessageDrivenBean (消息 bean)的引入也是非常重要的。消息 bean 体现出 JMS (Java Message Service)与 EJB 相集成,以创建出一种全新的 bean 类型,它设计用来处理异步的 JMS 消息。这种振奋人心的新型 bean 为 JMS 客户机提供一种组件模型,允许将它们部署到 EJB 容器系统的丰富而强健的环境中去。

对该规范还作了许多较小的其它更改。这些其它更改虽然也重要,但它们主要是涉及使该规范更严格,以便消除多义性,并使这些组件具有更高的可移植性。本文集中讨论 EJB 2.0 中引入的新 CMP 和消息 bean 组件模型。

我将提供几个具体的例子,所以读者应该很容易跟上并理解它。但是,EJB 初学者可能发现这个材料比较困难,因为它假定读者已对 EJB 有了基本的了解。有关 EJB 的详细信息,请参阅参考资料。

容器管理的持久性
容器管理的持久性在 EJB 2.0 中发生了根本变化。在 EJB 2.0 中,持久性管理器在运行时自动处理 CMP 实体 bean 的持久性。持久性管理器负责根据一种称为抽象持久性方案的新的 bean 持久性管理器合约,将实体 bean 映射到数据库。此外,持久性管理器还负责实现和执行多种查找方法,这些查找方法均基于一种称为 EJB QL 的新型查询语言。

注意到以下事实是很重要的,即符合 EJB 2.0 规范的产品必须能支持 EJB 1.1 CMP 模型,又能支持新的 EJB 2.0 模型。虽然这两种模型并不兼容,但是为了保证向后兼容性,就必须能支持 EJB 1.1 模型。

抽象持久性方案
为了理解抽象持久性方案是如何工作的,以及它为什么重要,我将为您快速地回顾一下在 EJB 1.1 中是如何处理 CMP 的,随后再讨论在 EJB 2.0 中如何定义它。

EJB 1.1 中的 CMP 模型
在 EJB 1.1 中,bean 开发人员负责将 bean 类的持久性字段声明为 Java 基本类型或可序列化类型。下列示例显示了一个 Employee 企业级 bean 类,它是按 EJB 1.1 定义的,带有几个 CMP 字段:

// Employee bean 类
public class EmployeeBean implements
java.ejb.EntityBean {
// 实例字段
EntityContext ejbContext;

// 容器管理的字段
public int identity;
public String firstName;
public String lastName;
public double salary;
public Address address;

public Integer ejbCreate(int id, String fname,
String lname){
identity = id;
firstName = fname;
lastName = lname;
return null;
}
...
}
// Address 从属类
public class Address implements Serializable{
public String street;
public String city;
public String state;
public String zip;
}



当将关系数据库用于持久性时,基本字段如 identity、firstName、lastName 和 salary,很容易持久化,因为它们很好地映射为 SQL 类型,如 INTEGER、CHAR 和 DOUBLE。

在 EJB 1.1 中,CMP bean 的 XML 部署描述符提供 cmp-field 元素,用以标识此 bean 类中的持久性字段(容器管理的字段)。如下所示,cmp-field 元素用来区分写入数据库的字段和不写入数据库的字段。例如,ejbContext 字段就不包括在容器管理的字段的列表中,因此它不是持久性字段。

<ejb-jar>
<enterprise-beans>
<entity>
<ejb-name>EmployeeEJB</ejb-name>
...
<persistence-type>Container</persistence-type>

...
<cmp-field><field-name>identity</field-name></cmp-field>

<cmp-field><field-name>firstName</field-name></cmp-field>

<cmp-field><field-name>lastName</field-name></cmp-field>

<cmp-field><field-name>salary</field-name></cmp-field>

<cmp-field><field-name>address</field-name></cmp-field>

...


容器提供者提供一种工具,用来将 bean 的持久性字段映射到数据库表中的列,通常每个 bean 对应一个表。但是,可序列化的类型,如 Address,就比较难于持久化。在 EJB 1.1 中,没有标准的方法将可序列化的对象映射到关系数据库。虽然 Address 类有其自身的字段集,但 XML 部署描述符并没有提供一种机制,来将这些字段映射到数据库。在大多数情况下,人们期望将可序列化的对象(如 Address)作为二进制类型(有时称为 blob 类型)持久化到某个数据库表中。

由于实体 bean 的数据方案逐渐复杂起来,所以这个问题也变得严重了。例如,Employee bean 可能有多个类似于 Address 的子对象,如 Benefits 和 JobPosition。这些子对象称为从属对象,可以形成关系数据库中跨几个表的复杂对象图。另外,EJB 1.1 中的 CMP 在很大程度上不足以持久化与其它 bean 的关系。在 EJB 1.1 中,如果某个 bean 准备维持与另一个 bean 的关系,则容器会自动将主关键字或句柄用作一个链接。与某些其它 bean 的关系其性质可能是双向的,或者要依赖于一些不易用主关键字或句柄来表示的字段,为了保持与这类 bean 的关系,上面的办法已被证明是一种远未完善的机制。

EJB 2.0 的 CMP 模型
在 EJB 2.0 中,CMP 实体 bean 和持久性管理器之间的新合约,使您能够在实体 bean 中定义更复杂的、可移植性更强的关系,包括 bean 与 bean 之间、bean 与从属对象之间、甚至从属对象与从属对象之间的关系。

持久性管理器是新加入到 Enterprise JavaBeans 部署过程中的。容器厂商,或专长于特定数据库的持久性的厂商,将能提供这种持久性管理器。其思路是将用于管理 bean 关系的机制从容器中分离出来,容器只负责管理安全、事务和资源。这种职责上的分离使不同的持久性管理器能够与不同的容器一起工作。它也使实体 bean 在不同 EJB 厂商之间以及在各种持久性管理器之间具有更强的可移植性。

如果您使用或学习过 Thought Inc. 生产的,能自动为 EJB 1.1 容器生成 BMP(bean 管理的持久性)bean 的产品 CocoBase,则您对持久性管理器工具如何工作就已经比较熟悉了。CocoBase 根据 bean 部署者提供的,从对象到关系的映射信息,为 BMP bean 生成全部数据库访问逻辑。在 EJB 2.0 中,持久性管理器能够根据部署描述符、bean 的抽象持久性方案和部署者完成的工作所提供的信息,生成 CMP 实体到关系数据库的映射。但是,持久性管理器并不局限于关系数据库。也可以为对象数据库以及遗留的系统和 ERP 系统(如 SAP)开发持久性管理器。

为了将持久性管理器从容器中分离出来,必须定义 bean 与持久性管理器之间的合约。这个合约在新的抽象持久性方案中表现出来。此方案是通过部署描述符中一组新的 XML 元素和 CMP 实体 bean 中的一组代码习语定义的。在 EJB 2.0 中,CMP bean 类被声明为抽象类,它的持久性字段和关系字段是使用抽象的读方法和写方法来访问的,而这两种方法的方法特征则映射为 XML 部署描述符中的特定元素。

在部署该 bean 时,您将使用持久性管理器工具,根据 XML 部署描述符和 bean 类,来具体实现此抽象 bean 类及其从属对象类。具体实现将包括数据访问代码,此代码将在运行时将 bean 的状态实际读出和写到数据库中。在运行时,容器使用由持久性管理器工具生成的子类,而不使用 bean 提供者定义的抽象类。

bean 类的继承层次结构


为了使讨论更充实,这里提供一个 CMP 实体的示例,它更具体地说明了抽象持久性方案是如何工作的。

EJB 2.0 中的一个示例 CMP 实体
在 EJB 2.0 中,容器管理的实体 bean 被定义为抽象的,而且它的持久性字段并不在 bean 类中直接定义。作为替代,开发了一种抽象的持久性方案,从而允许 bean 提供者间接地声明持久性字段和 bean 关系。下面是 Employee bean 的一个示例,它使用了新的抽象持久性方案。请注意,该 bean 类中未声明任何持久性字段。

public abstract EmployeeBean implements
javax.ejb.EntityBean {
. // 实例字段
EntityContext ejbContext;

// 容器管理的持久性字段
public abstract void setIdentity(int
identity);
public abstract int getIdentity();
public abstract void setFirstName(String
firstName);
public abstract String getFirstName();
public abstract void setLastName(String
lastName);
public abstract String getLastName();

// 容器管理的关系字段
public abstract void
setContactInfo(ContactInfo info);
public abstract ContactInfo
getContactInfo();

...
}


在此 bean 的 XML 部署描述符中,抽象的持久性方案声明容器管理的各个字段和各种关系。

<ejb-jar>
<enterprise-beans>
<entity>
<ejb-name>EmployeeEJB</ejb-name>
...
<persistence-type>Container</persistence-type>

...
<cmp-field><field-name>identity</field-name></cmp-field>

<cmp-field><field-name>firstName</field-name></cmp-field>

<cmp-field><field-name>lastName</field-name></cmp-field>

...
</entity>
</enterprise-beans>
<dependents>
<dependent>
<dependent-class>ContactInfo</dependent-class>

<dependent-name>ContactInfo</dependent-name>

<cmp-field>street</cmp-field>
<cmp-field>city</cmp-field>
<cmp-field>state</cmp-field>
<cmp-field>zip</cmp-field>
<cmp-field>homePhone</cmp-field>
<cmp-field>workPhone</cmp-field>
<cmp-field>email</cmp-field>
...
</dependent>
<relationships>
<ejb-relation>
<ejb-relation-name>Employee-ContactInfo</ejb-relation-name>

<ejb-relationship-role>
<ejb-relationship-role-name>
employee-has-contactinfo

</ejb-relationship-role-name>
<multiplicity>one</multiplicity>
<role-source>
<ejb-name>EmployeeEJB</ejb-name>

</role-source>
<cmr-field>
<cmr-field-name>contactInfo</cmr-field-name>

<cmr-field-type>ContactInfo</cmr-field-type>

</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<ejb-relationship-role-name>
contactinfo_belongsto_employee

</ejb-relationship-role-name>
<multiplicity>one</multiplicity>
<role-source>
<dependent-name>ContactInfo<dependent-name>

</role-source>
</ejb-relationship-role>
</ejb-relation>
</relationships>
<ejb-jar>


用来描述容器管理的关系的 XML 元素可能变得非常复杂,因为他们必须处理各种关系的对应性和方向(单向的还是双向的)。上面的代码段说明,为了描述 bean 与其从属对象类之间的简单关系,您需要哪些元素。虽然即使是简单的关系也会被转换为冗长的 XML,但所有这些元素都是必需的,以便持久性管理器能够将复杂的对象图映射到数据库中。

虽然用于定义 CMP bean 的抽象持久性方案的 XML 元素是 EJB 2.0 中的 CMP 的主要问题,但为了简洁起见,本文不再提供 XML 示例。作为替代,本文将纯粹依靠 bean 类中必须使用的抽象习语,来说明 EJB 2.0 中的 CMP 背后的基本概念。这些代码习语与 XML 部署描述符中的关系元素一起使用,并由后者定义,所以您不能只有其一而没有另一个,但它们比该方案的 XML 部分较容易理解。

除了 XML 元素之外,抽象的持久性方案还定义了一组习语,它们在声明 bean 类及其相关的对象时必然会用到。用来访问和修改字段的方法是严格定义了的,要求用 set<METHOD> 方法修改持久性字段,而用 get<METHOD> 方法访问它们。这些方法的名称和返回类型由部署描述符中它们相应的 XML 关系元素规定。

实体 bean 类和从属类都遵循相同的抽象持久性方案。下面是如何将 ContactInfo 对象定义为从属对象类的示例。

public abstract class ContactInfo {
// 家庭地址信息
public abstract void setStreet(String street);
public abstract String getStreet();
public abstract void setState(String state);
public abstract String getState();
public abstract void setZip(String zip);
public abstract String getZip();
public abstract void setHomePhone(String phone);
public abstract String getHomePhone();

// 工作地址信息
public abstract void setWorkPhone(String phone);
public abstract String getWorkPhone();
public abstract void setEMail(String email);
public abstract String getEMail();
...
}


从属对象随实体 bean 的存在而存在,随实体 bean 的中止而中止,这是理解从属对象与实体 bean 之间关系的关键。从属对象包含在一个具体的实体中,所以删除这个实体将导致从属对象也被删除。用关系数据库的术语来说,有时这就称为级联删除。

从属对象,如 ContactInfo,用在关系字段中。与实体 bean 形成关系的从属对象技术上称为从属对象类。EJB 客户端应用程序永远不能直接访问从属对象类;这种类不能用作 bean 的远程或本地接口中的参数或返回值。从属对象类只对 bean 类才是可见的。

从属对象类不适合作为远程参数类型,因为它们与 bean 在运行时的持久性逻辑有密切的联系。持久性管理器扩展了抽象的从属对象类,以便能提供一种实现,可用于在运行时管理 bean 的持久性状态。此外,抽象的持久性方案还为数据建模 -- 而不是为那些由企业级 bean 表示的业务概念建模 -- 所以,作为一种设计策略,将抽象的持久性方案对 EJB 客户机隐藏起来是有意义的。

例如,ContactInfo 关系字段中除了 bean 的客户机所需的简单地址信息之外,还包含许多其它信息。虽然您可以使用抽象持久性方案中的从属对象类 ContactInfo(它对 bean 的客户机是隐藏的),但是,您得用其它的对象来把这些数据实际表露给客户机。下面是一个示例,说明了如何对 EJB 客户机隐藏 ContactInfo 从属对象。在此例中,地址信息是通过在 EJB 1.1 的示例中开发的 Address 对象来表露的。

// Employee bean 的远程接口
public interface Employee extends javax.ejb.EJBObject {

public Address getHomeAddress();
public void setHomeAddress(Address address);

public int getIdentity() throws RemoteException;
public void setFirstName(String firstName) throws
RemoteException;
public String getFirstName()throws RemoteException;
public void setLastName(String lastName) throws
RemoteException;
public String getLastName() throws RemoteException;
}

// Employee bean 的 bean 类
public abstract EmployeeBean implements
javax.ejb.EntityBean {
...
public Address getHomeAddress(){
ContactInfo info = getContactInfo();
Address addr = new Address();
addr.street = info.getStreet();
addr.city = info.getCity();
addr.state = info.getState();
addr.zip = info.getZip();
return addr;
}
public void setHomeAddress(Address addr){
ContactInfo info = getContactInfo();
info.setStreet(addr.street);
info.getCity(addr.city);
info.getState(addr.state);
info.getZip(addr.zip);
}
....
// 容器管理的关系字段
public abstract void setContactInfo(ContactInfo
info);
public abstract ContactInfo getContactInfo();
...
}


尽管容器管理的关系字段没有表露给客户机,但您仍然可以从远程接口直接使用容器管理的持久性字段。请注意,用来访问 firstName 和 lastName 的容器管理的持久性字段是在远程接口中使用的。

一个 bean 与各种从属对象类之间可能具有多种不同的关系,它们由这种关系的对应性和方向来定义。Bean 与从属对象类之间可以有一对多和一对一的关系。例如,Employee bean 可能仅有一个 Benefit 从属对象类,但可能有许多 ContactInfo 从属对象类。

public abstract EmployeeBean implements
javax.ejb.EntityBean {
...
public abstract void setContactInfos(Collection
addresses);
public abstract Collection getContactInfos():

public abstract void setBenefit(Benefit benefit);

public abstract Benefit getBenefit();
...
}


与从属对象类的一对多关系既可表示为 java.util.Collection 类型,也可表示为 ava.util.Set 类型(注:在本规范的后续版本中,java.util.Map 和 java.util.List 被视为附加的返回类型),而与从属对象的一对一关系则使用从属对象的类型。

实体 bean 也可以定义与其它实体 bean 的关系。这些关系可以是一对一、一对多或多对多。例如,Employee bean 可能有许多子级 bean,而只有一个配对的 bean。下面的代码段使用抽象持久性方案的方法习语,说明了如何为这些关系建模。该应用程序中,子级 bean 和配对的 bean 都表现为 Person bean。

public abstract EmployeeBean implements
javax.ejb.EntityBean {

...
public abstract void setSpouse(Person manager);
public abstract Person getSpouse();

public abstract void setChildren(Collection
family);
public abstract Collection getChildren();
...
}


与另一个 bean 的一对多关系表示为 java.util.Collection 类型或 java.util.Set 类型,而一对一关系则使用该 bean 的远程接口类型。

从属对象本身与同一个 bean 中的其它从属对象之间可以有一对一、一对多和多对多的关系。此外,从属对象与其它实体 bean(除其父级 bean 之外)也可以有一对一、一对多的关系。下面的示例显示,Benefit 从属对象类与 Salary 从属对象(一种报酬计算程序)之间怎样具有一对一的关系,而与 Investment bean 又怎样具有一对多的关系。

public abstract class Benefit {
public abstract void setSalary(Salary salary);
public abstract Salary getSalary();

public abstract void setInvestments(Collection
investments);
public abstract Collection getInvestments();
}


在部署时,部署者将使用持久性管理器工具来具体实现这个 bean 类及其从属类。这些具体实现将在运行时保持各种关系,并使各 bean 实例的状态与数据库同步。容器将在运行时管理持久性实例,从而提供一种强健的环境,其中具有自动的访问控制和事务控制。

bean 也可以定义从属对象的值,这些对象是可序列化的对象,如 EJB 1.1 示例中的 Address 对象。这些值通过序列化而变为持久的,它们并不形成与 bean 的关系 -- 它们是严格的容器管理的持久性字段。

容器与持久性管理器之间也已经定义了一个合约,使持久性管理器可以获得事务的句柄,并访问由该容器管理的数据库连接池。这个合约稍嫌宽松,将来还需要使其更为严格,但它是允许持久性管理器跨 EJB 容器移植的基础。容器和持久性管理器之间合约的细节已超出了本文的范围。

除了通过抽象持久性方案定义持久性之外,EJB 2.0 还提供了一种新的查询语言,用来说明持久性管理器应该如何实现 CMP 中的各种查找方法。

EJB 查询语言
EJB 查询语言 (EJB QL) 规定了持久性管理器应该如何实现在本地接口中定义的各种查找方法。 EJB QL 以 SQL-92 为基础,可由持久性管理器自动编译,这使得实体 bean 具有更高的可移植性,并且更容易部署。

EJB QL 和查找方法
EJB QL 语句是在实体 bean 的部署描述符中声明的。使用 EJB QL 非常简单。作为一个例子,Employee bean 的本地接口可以按以下方式声明:

public interface EmployeeHome extends javax.ejb.EJBHome
{
...

public Employee findByPrimaryKey(Integer id)
throws RemoteException, CreateException;

public Collection findByZipCode(String zipcode)
throws RemoteException, CreateException;

public Collection findByInvestment(String
investmentName)
throws RemoteException, CreateException;

}



给定了上面的本地接口定义之后,您就可以使用 EJB QL 来指定持久性管理器应该如何执行查找方法。每个实体 bean 都必须有一个 findByPrimaryKey() 方法。为执行该方法所需的查询是很明显的 -- 使用主关键字的(一个或几个)字段在数据库中查找 bean,这样就不需要任何 EJB QL 语句。

findByZipCode() 方法用来获得具有某个邮政编码的所有 Employee bean。这将使用部署描述符中的下列 EJB QL 来表达。

FROM contactInfo WHERE contactInfo.zip = ?1

该语句本质上是表示“选择其邮政编码等于 zipcode 参数的所有 Employee bean”。

在用于查找方法的 EJB QL 语句中,不需要使用 SELECT 子句来表明要选择的内容。这是因为,查找方法将总是选择与其自身的 bean 类型相同的远程引用。在这种情况下,就可以认为选择语句将返回远程 Employee bean 的全部引用。

如果各种查找方法都一起部署在同一个 ejb-jar 文件中,并且其间具有可导航的实际关系,那么这些查找方法就甚至可以跨越到另一些 bean 的抽象持久性方案中去。例如,findByInvestment() 方法将要求该查找查询从 Employee 导航到投资 bean 的抽象持久性方案中去。声明来表达这种查找操作的 EJB QL 语句如下所示。

FROM element IN benefit.investments WHERE element.name
= ?1


以上语句是说:“选择全部这样的 Employee bean:其获利从属对象至少包含一个投资 bean 的引用,并且其名称等于 findByInvestment() 方法的 investmentName 参数。”

EJB QL 和选择方法
EJB QL 也用于一种称为 ejbSelect 方法的新查询方法中,该方法类似于查找方法,只是它仅供 bean 类使用。该方法不在本地接口中声明,所以也不显露给客户机。此外,ejbSelect 方法可返回范围更大的各种值,而不仅限于 bean 本身的远程接口类型。

存在两种选择方法:ejbSelect<METHOD> 和 ejbSelect<METHOD>InEntity。ejbSelect<METHOD> 方法是全局执行的,这是指这种方法并非专用于执行该方法的 bean 实例。ejbSelect<METHOD>InEntity 方法则专用于执行该方法的实体实例。这些选择方法在 bean 类中被声明为抽象方法,并在这些类的业务方法中使用。下面是 ejbSelect<METHOD> 方法和 ejbSelect<METHOD>InEntity 方法的示例,同时说明了可以如何在业务方法中使用它们。

public abstract class EmployeeBean implements
javax.ejb.EntityBean {
...
// ejbSelectInEntity
public abstract Collection
ejbSelectInvestmentsInEntity (String risk);

// ejbSelect
public abstract Collection
ejbSelectInvestments(String risk);
...
}


在上面的声明中,两种选择方法运行于不同的范围。ejbSelectInvestmentsInEntity() 仅在当前的 Employee bean 实例上执行,所以它只返回雇员的风险投资。

SELECT invest FROM invest IN benefit.investments WHERE
invest.type = ?1


另一方面,ejbSelect<METHOD> 方法的范围则是全局性的,所以同一个查询将返回整个企业内所有雇员的全部风险投资。

ejbSelect<METHOD>InEntity 选择方法可以返回 bean 的远程类型(如在上面的查询中那样)、从属对象或任何其它 Java 类型。另一方面,全局选择方法则不能返回 bean 的从属对象类型。

选择方法的 EJB QL 语句要求使用 SELECT 子句,因为它们能够返回范围更广的各种值。

新的 ejbHome 方法
在 EJB 2.0 中,实体 bean 可以声明一些 ejbHome 方法,用来执行与 EJB 组件相关的操作,但并不专用于某个 bean 实例。在 bean 类中定义的 ejbHome 方法在本地接口中必须有一个与其相匹配的本地方法。下面的代码说明了一个本地方法,它正是作为 Employee bean 的本地接口定义的。applyCola() 方法用来根据最近 COLA(生活费用调整)的增长来更新所有雇员的薪水。


public interface EmployeeHome extends javax.ejb.EJBHome
{
// 本地方法
public void applyCola(double increate) throws
RemoteException;
...
}


applyCola() 方法在 bean 类中必须有匹配的 ejbHome 方法,它被声明为 ejbHomeApplyCola()。ejbHomeApplyCola() 方法并非专用于一个 bean 实例,它的范围是全局的,所以它将对所有雇员的薪水使用同一个 COLA。

public abstract class EmployeeBean implements
javax.ejb.EntityBean {
...
// ejbHome 方法
public void ejbHomeApplyCola (double increase ){
Collection col = ejbSelectAllEmployees ();
Iterator employees = col.iterator();
while(employees.next()){
Employee emp =
(Employee)employees.next();
double salary =
emp.getAnnualSalary();
salary = salary + (salary*increase);
emp.setAnnualSalary(salary);
}
}
}


bean 的开发人员需要为 BMP 和 CMP 实体 bean 都实现 ejbHome 方法。CMP 实现可能在很大程度上要依赖于全局的选择语句(如上面所说明的那样)和 finder 方法,而 ejbHome 的 BMP 实现则将使用直接数据库访问和 bean 的 finder 方法,来查询数据和进行更改。

MessageDrivenBean
在 EJB 2.0 中,对规范的一个基础性更改是添加了一种全新的企业级 bean 类型,即 MessageDrivenBean。MessageDrivenBean 专门设计来处理入网的 JMS 消息。对于许多开发人员来说,JMS 是一种新的范例,所以本文将花一些时间逐步说明对 JMS 的理解,以及它们在 EJB 2.0 中的用法。

什么是 JMS?
JMS 是一种与厂商无关的 API,用来访问消息收发系统。它类似于 JDBC (Java Database Connectivity):这里,JDBC 是可以用来访问许多不同关系数据库的 API,而 JMS 则提供同样与厂商无关的访问方法,以访问消息收发服务。许多厂商目前都支持 JMS,包括 IBM 的 MQSeries、BEA 的 Weblogic JMS service 和 Progress 的 SonicMQ,这只是几个例子。

JMS 使您能够通过消息收发服务(有时称为消息中介程序或路由器)从一个 JMS 客户机向另一个 JML 客户机发送消息。消息是 JMS 中的一种类型对象,由两部分组成:报头和消息主体。报头由路由信息以及有关该消息的元数据组成。消息主体则携带着应用程序的数据或有效负载。根据有效负载的类型来划分,可以将消息分为几种类型,它们分别携带:简单文本 (TextMessage)、可序列化的对象 (ObjectMessage)、属性集合 (MapMessage)、字节流 (BytesMessage)、原始值流 (StreamMessage),还有无有效负载的消息 (Message)。

消息收发系统是异步的,也就是说,JMS 客户机可以发送消息而不必等待回应。比较可知,这完全不同于基于 RPC 的(基于远程过程的)系统,如 EJB 1.1、CORBA 和 Java RMI 的引用实现。在 RPC 中,客户机调用服务器上某个分布式对象的一个方法。在方法调用返回之前,该客户机被阻塞;该客户机在可以执行下一条指令之前,必须等待方法调用结束。在 JMS 中,客户机将消息发送给一个虚拟通道(主题或队列),而其它 JMS 客户机则预订或监听这个虚拟通道。当 JMS 客户机发送消息时,它并不等待回应。它执行发送操作,然后继续执行下一条指令。消息可能最终转发到一个或许多个客户机,这些客户机都不需要作出回应。

EJB 2.0 中的 JMS
EJB 2.0 以两种方式支持 JMS 的集成:作为一种 bean 可用的资源,和作为一个 MessageDrivenBean。当将 JMS 用作一种资源时,使用 JMS API 的 bean 就是消息的产生者或发送者。在这种情况下,bean 将消息发送给称为主题或队列的虚拟通道。另一方面,MessageDrivenBean 则是消息的使用者或接收者。它监听特定的虚拟通道(主题或队列),并处理发送给该通道的消息。为了更好地理解消息产生者和消息使用者的作用,用 SessionBean bean 来发送一条使用 JMS 的消息,然后使用一个新的 MessageDrivenBean 来使用该同一条消息。

作为 EJB 2.0 资源的 JMS
会话 bean 和实体 bean 都是基于 RPC 的组件,为了将各种事务性的组件装配到一起,这是一种卓越的体系结构。但是,在某些情况下,RPC 的同步性质会成为一种障碍,这正是 EJB 1.1 中将对 JMS API 的访问作为一种资源包括在内的原因。利用 JNDI 环境命名的上下文,bean 可以获得一个 JMS 工厂,并将一条异步消息发送给主题或队列(也从 JNDI 获得),而不必等待回应。下面是 ShoppingCart bean 的一个例子,它使用 JMS 将 Order 的详细信息发送给消息收发主题。

public class ShoppingCartBean implements SessionBean {
// 订单详细信息是一个可序列化的对象,它包含全部订单信息。
public OrderDetail orderDetail;

public void processOrder(){

// 处理订单的逻辑从此处开始
....

// ... 处理订单以后,向其它系统发送有关此订单的一条消息
InitialContext jndiEnc = new
InitialContext();

// 使用 JNDI ENC 获取 JMS 工厂和主题标识符
TopicConnectionFactory factory =
jndiEnc.lookup("java:comp/env/jms/topicfactory");

Topic orderTopic =
jndiEnc.lookup("java:comp/env/jms/ordertopic");

// 获得一个用来发送消息的发布者
TopicConnection con =
factory.createTopicConnection();
TopicSession session =
con.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE );
TopicPublisher publisher =
session.createPublisher(orderTopic);

// 将一个 ObjectMessage 发送给主题(虚拟通道)
ObjectMessage message =
session.createObjectMessage();
message.setObject(orderDetail);
publisher.publish(message);
con.close();
}
...
}


在这种情况下,JMS 是用来通知另外的应用程序,订单已被处理。这些另外的应用程序对于处理订单来说并不重要,但它们会因为得到一个订单已被处理的通知而受益。这样的例子包括自动调整库存的库存系统,和能将客户添加进产品目录邮寄名单中的销售应用程序。

使用 JMS 使 bean 能够发布(发送)消息而不会发生阻塞。bean 并不知道谁将收到消息,因为它是将消息发送给某个主题(虚拟通道),而不是直接发送给另一个应用程序。应用程序可以选择预订该主题,并接收有关新订单的通知。这样就有可能动态地在虚拟通道中添加或删除应用程序,从而产生了一种更加灵活的系统。

预订了订单主题的应用程序将收到有关新订单的通知,应用程序可以使用它们认为合适的任何方式来处理这个通知。预订了各种主题的应用程序或者从各个队列中接收消息的应用程序可以是 Java 应用程序、EAI 系统(用于集成遗留系统和 ERP 系统)或者 MessageDrivenBean 组件,在 JMS 的术语中,它们全部被认为是 JMS 客户机。

JMS 和 MessageDrivenBean
虽然大多数 JMS 厂商都提供消息中介工具,来将消息从发送者路由到接收者,但构建使用(接收)消息的 JMS 客户机却是应用程序开发人员的职责。在许多情况下,接收消息的应用程序必须强健、安全、快速而且可伸缩;它需要的基础结构基本上与 EJB 应用程序相同。

由于认识到这种需要,EJB 2.0 现在包括了 MessageDrivenBean 类型,它可以使用 JMS 消息,并且在同一个强健的、基于组件的基础结构中处理这些消息,这样的基础结构对于会话 bean 和实体 bean 都非常有用。MessageDrivenBean 类型(消息 bean)是一种企业级 bean 组件,它设计来使用异步的 JMS 消息。

除了提供容器基础结构以外,EJB 还具有另一个重要的优点:并发处理。在 EJB 中,一个已部署的消息 bean 表示一个单一的消息使用者,但这个 bean 本身是由许多 bean 实例提供服务的。每个 bean 实例都可以分别地使用消息 bean 接收到的消息。这意味着,消息 bean 不必像常规 JMS 客户机那样连续地使用消息。消息 bean 可以并发地使用接收到的多个消息,这样就能达到比传统 JMS 应用程序高得多吞吐量和好得多的可伸缩性。

为了说明消息 bean 的作用,就开发了 MarketingBean 类,并将它从订单主题中部署到供使用的消息中去。MarketingBean 将从消息中提取 OrderDetail 对象,并使用它将客户添加到适当的目录邮寄名单中。这是一种最精致的大量邮寄系统。

下面是 MarketingBean 类的定义,这个类使用发布给订单主题的消息。

public class MarketingBean implements
javax.ejb.MessageDrivenBean {

public void onMessage(Message message) {

ObjectMessage orderMessage =
(ObjectMessage)orderMessage:
OrderDetail orderDetail =
(OrderDetail)orderMessage.getObject();

Integer customerID =
orderDetail.getCustomerID();

InitialContext jndiEnc = new
InitialContext();
CatalogHome catalogHome =
(CatalogHome)jndiEnc.lookup("java:comp/env/ejb/catalog");


Iterator productIDs =
orderDetail.getProductsIDs();
while(productIDs.hasNext()){
Integer productID =
(Integer)productIDs.next();
Catalog cat =
CatalogHome.findByProductID(productID);
cat.addCustomerToMailingList(customerID);
}
}
}


正像会话 bean 和实体 bean 一样,MessageDrivenBean 也是一种完备的企业级 bean,但其间仍存在一些重要的区别。消息 bean 没有远程接口或本地接口。这是因为消息 bean 不是 RPC 组件。它没有供 EJB 客户机调用的业务方法。消息 bean 监听虚拟消息通道(主题或队列),并使用其它 JMS 客户机发送给该通道的消息。

各个消息 bean 构成一个 bean 类,这个类实现 MessageDrivenBean 接口和一个 XML 部署描述符。下面是 MessageDrivenBean 接口的定义,所有消息 bean 都必须实现这个接口。

package javax.ejb;
import javax.jms.Message;
import javax.jms.MessageListener;

public interface MessageDrivenBean extends
MessageListener{
public void onMessage(Message message);
public void ejbCreate();
public void ejbRemove();
public void
setMessageDrivenContext(MessageDrivenContext mdc);
}


当部署了一个消息驱动的 bean 以后,它就被指派来处理特定主题或队列中的消息。JMS 客户机(Java 应用程序、bean 或本地客户机)发送的任何消息,将由消息路由器转发给消息 bean,该消息 bean 正是被指派来从该虚拟通道中接收消息的。当一条消息被发送给一个消息 bean 时,EJB 容器就会从某个池中选择该 bean 的一个实例,来处理这条消息。当 bean 实例调用其 onMessage() 方法时,它就会接收到这条消息,并能够以它认为合适的任何方式来处理这条消息。一旦这条消息被使用,则只要事务没有异常中止,这条消息都不会被传送给这个消息 bean 的任何其它实例。

消息 bean 在某点上类似于无状态的会话 bean,即这两种 bean 在两次请求之间都不保持任何状态。因此,消息驱动的 bean 是无状态的,但是,就像无状态的会话 bean 一样,它们也可以有实例变量,这些变量在这个 bean 实例的整个生存期内均保持。

对消息 bean 的最后一点说明是,理解这样一个事实是很重要的,即 bean 使用的消息不一定要是由其它 bean 所产生的。消息 bean 可以使用由符合 JMS 的厂商提供的任何主题或队列中的消息。消息 bean 使用的消息可以来自其它 bean(会话 bean、实体 bean 或消息 bean)、非 EJB 的 Java 应用程序、或者甚至非 Java 的应用程序(如果其供应商符合 JMS)。例如,遗留应用程序可能使用 IBM 的 MQSeries 向队列发送消息,而该消息既可以由其它遗留应用程序使用,同样可以由消息 bean 使用。

结论
与以前的规范相比,Enterprise JavaBeans 2.0 中作了一些相当大的更改。新的 CMP 模型比以前的模型要灵活得多,它允许各种实体为复杂的对象图建立模型,而同又提供跨容器的更大的可移植性。人们迫切地期待着为查找和选择操作定义一种通用的查询语言,而它也将有助于提高可移植性。

这种新的 MessageDrivenBean 类型将有助于使这种强大的消息收发范例成为众人瞩目的焦点,就像 EJB 那样。消息收发在分布式的混合计算中是一个极其重要的组成部分,将它包括在 EJB 内就是其重要性的一个证明。

在写这篇文章时,EJB 2.0 刚刚作为公开草案发布,这意味着在它成为一个最终规范之前仍有可能更改。如果更改对此处提供的材料有重大影响,届时我将设法对本文作一些注释,但这个规范正在趋于稳定,所以不太可能有真正重大的更改。

参考资料

"A Beginner's Guide to Enterprise JavaBeans," Mark Johnson(JavaWorld,1998 年 10 月):
Richard Monson-Haefel 的 EJB 开发者网站,EJBNow.com
EJB 2.0,规范
Thought 的 CocoBase
IBM 的 MQ Series
BEA 的 WebLogic JMS Service
Progess Sonic MQ
Richard Monson-Haefel 所写的其它文章:

"Create forward-compatible beans in EJB, Part 1"(JavaWorld,1999 年 12 月)
"Create forward-compatible beans in EJB, Part 2"(JavaWorld,2000 年 1 月)
作者简介
Richard Monson-Haefel 是最近发布的 Enterprise JavaBeans 第二版的作者。他是 OpenEJB 的首席设计师(OpenEJB 是一种开放源代码的 Enterprise JavaBeans 2.0 容器),他曾经以设计师身份为 Enterprise JavaBeans、CORBA、Java RMI 以及其它 Java 方案提供咨询。Monson-Haefel 还维护着一个网站,供人们讨论 Enterprise JavaBeans 和相关的分布式计算技术。可以通过 richard.monson-haefel@javaworld.com 与 Richard Monson-Haefel 联系。

23,407

社区成员

发帖
与我相关
我的任务
社区描述
Java 非技术区
社区管理员
  • 非技术区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧