您正在查看: siddim 发布的文章

Enterprise JavaBeans

Server/Container发布我们刚刚编写好的EJB 以便进行测试测试成功以后再把它分发

到真正的EJB Server 上去

首先需要运行Smart Agent 即osagent.exe 程序osagent.exe 程序在IAS 安装目录的

bin 文件夹内

然后转回到JBuilder4 的主界面选择Run Run Project JBuilder4 首先会编译EJB

然后把它分发到Smart Agent 中JBuilder4 会出现类似于下面的提示信息

Inprise EJB Container

________

第2 章 EnterpriseJavaBeans

=====================

server version : 4.1.1

server build date : Aug 18 2000

java version : 1.3.0

java vendor : Sun Microsystems Inc.

heap size : 1984 Kb

java class path : D:\Inprise\AppServer\lib\navigator.jar

: D:\Inprise\AppServer\lib\vbdev.jar

: D:\Inprise\AppServer\lib\vbejb.jar

: D:\Inprise\AppServer\lib\vbjdev.jar

: D:\Inprise\AppServer\lib\vbjorb.jar

: D:\Borland\JBuilder4\test\HelloWorld\classes

: D:\Borland\JBuilder4\IAS\lib\ias.jar

: D:\Borland\JBuilder4\IAS\lib\jmclient.jar

: D:\Borland\JBuilder4\IAS\lib\jmserver.jar

: D:\Borland\JBuilder4\IAS\lib\migration.jar

: D:\Borland\JBuilder4\IAS\lib\navigator.jar

: D:\Borland\JBuilder4\IAS\lib\pjbean.jar

: D:\Borland\JBuilder4\IAS\lib\servlet.jar

: D:\Borland\JBuilder4\IAS\lib\vbdev.jar

: D:\Borland\JBuilder4\IAS\lib\vbejb.jar

: D:\Borland\JBuilder4\IAS\lib\vbjdev.jar

: D:\Borland\JBuilder4\IAS\lib\vbjorb.jar

: D:\BORLAND\JBUILDER4\JDK1.3\demo\jfc\Java2D\Java2Demo.jar

: D:\BORLAND\JBUILDER4\JDK1.3\jre\lib\i18n.jar

: D:\BORLAND\JBUILDER4\JDK1.3\jre\lib\jaws.jar

: D:\BORLAND\JBUILDER4\JDK1.3\jre\lib\rt.jar

: D:\BORLAND\JBUILDER4\JDK1.3\jre\lib\sunrsasign.jar

: D:\BORLAND\JBUILDER4\JDK1.3\lib\dt.jar

: D:\BORLAND\JBUILDER4\JDK1.3\lib\tools.jar

=====================

Initializing ORB........... done

Initializing JNS........ done

Initializing JTS.... done

Initializing JSS.....Developer's License (no connection limit)

Copyright (c) 1996-2000 Inprise Corporation. All rights reserved.

License for JDataStore development only - not for redistribution

Registered to:

Inprise Application Server Development Licensee

Inprise Application Server Customer

......... done

Initializing JDB................ done

Initializing EJBs......... done

________

第一部分 JSP 技术与J2EE 技术

Container [ejbcontainer] is ready

EJB Container Statistics

========================

Time Tue Mar 16 00:00:21 CST 1999

Memory (used) 1714 Kb (max 1714 Kb)

Memory (total) 2680 Kb (max 2680 Kb)

Memory (free) 36.0%

------------------------

Home HelloWorld

Total in memory 0

Total in use 0

========================

如果出现了上面的信息那么就表明这个HelloWorld EJB 已经成功地部署到Smart

Agent 中了下面我们应该编写一个简单的EJB 客户端程序来测试这个EJB 了

2.5.4 测试EJB 服务

本小节我们将介绍如何利用JBuilder4 来编写一个EJB 客户端测试我们刚才所发布的

EJB 在JBuilder4 中选择File New… Enterprise Tab EJB Test Client OK 将出现如

图2.14 所示的窗口

图2.14 JBuilder4 的EJB Test Client Wizard 对话框

在图2.14 中我们把Class 框的缺省值HelloWorldTestClient1 改为HelloWorldTestClient

其他的一切都不要改动单击OK 回到JBuilder4 的主界面JBuilder4 为我们自动创建了

HelloWorldTestClient.java 程序编辑HelloWorldTestClient.java 使得它如下面的程序清单

________

第2 章 Enterprise JavaBeans

2.13 所示其中黑体的部分是我们自己加上去的其余的部分是JBuilder4 自动产生的

程序清单2.13

package helloworld;

import javax.naming.*;

import javax.rmi.PortableRemoteObject;

/**

* Title:HelloWorldTestClient.java

* Description:test the EJB

* Copyright: Copyright (c) 1999

* Company:Peking University

* @author:fancy

* @version 1.0

*/

public class HelloWorldTestClient

{

private static final String ERROR_NULL_REMOTE = "Remote interface reference is null.It must be

created by calling one of the Home interface methods first.";

private static final int MAX_OUTPUT_LINE_LENGTH = 50;

private boolean logging = true;

private HelloWorldHome helloWorldHome = null;

private HelloWorldRemote helloWorldRemote = null;

/**Construct the EJB test client*/

public HelloWorldTestClient()

{

long startTime = 0;

if (logging)

{

log("Initializing bean access.");

startTime = System.currentTimeMillis();

}

try

{

//get naming context

Context ctx = new InitialContext();

//look up jndi name

Object ref = ctx.lookup("HelloWorld");

//cast to Home interface

helloWorldHome = (HelloWorldHome)

PortableRemoteObject.narrow(ref HelloWorldHome.class);

________

第一部分 JSP 技术与J2EE 技术

HelloWorldRemote hwr=create();

String msg=hwr.getMessage();

System.out.println(msg);

if (logging)

{

long endTime = System.currentTimeMillis();

log("Succeeded initializing bean access.");

log("Execution time: " + (endTime - startTime) + " ms.");

}

}

catch(Exception e)

{

if (logging)

{

log("Failed initializing bean access.");

}

e.printStackTrace();

}

}

//----------------------------------------------------------------------------

// Methods that use Home interface methods to generate a Remote interface reference

//----------------------------------------------------------------------------

public HelloWorldRemote create()

{

long startTime = 0;

if (logging)

{

log("Calling create()");

startTime = System.currentTimeMillis();

}

try

{

helloWorldRemote = helloWorldHome.create();

if (logging)

{

long endTime = System.currentTimeMillis();

log("Succeeded: create()");

log("Execution time: " + (endTime - startTime) + " ms.");

}

}

catch(Exception e)

{

if (logging)

{

________

第2 章 Enterprise JavaBeans

log("Failed: create()");

}

e.printStackTrace();

}

if (logging)

{

log("Return value from create(): " + helloWorldRemote + ".");

}

return helloWorldRemote;

}

//----------------------------------------------------------------------------

// Methods that use Remote interface methods to access data through the bean

//----------------------------------------------------------------------------

public String getMessage()

{

String returnValue = "";

if (helloWorldRemote == null)

{

System.out.println("Error in getMessage(): " + ERROR_NULL_REMOTE);

return returnValue;

}

long startTime = 0;

if (logging)

{

log("Calling getMessage()");

startTime = System.currentTimeMillis();

}

try

{

returnValue = helloWorldRemote.getMessage();

if (logging)

{

long endTime = System.currentTimeMillis();

log("Succeeded: getMessage()");

log("Execution time: " + (endTime - startTime) + " ms.");

}

}

catch(Exception e)

{

if (logging)

{

log("Failed: getMessage()");

}

________

第一部分 JSP 技术与J2EE 技术

e.printStackTrace();

}

if (logging)

{

log("Return value from getMessage(): " + returnValue + ".");

}

return returnValue;

}

//----------------------------------------------------------------------------

// Utility Methods

//----------------------------------------------------------------------------

private void log(String message)

{

if (message.length() > MAX_OUTPUT_LINE_LENGTH)

{

System.out.println("-- " +

message.substring(0 MAX_OUTPUT_LINE_LENGTH) + " ...");

}

else

{

System.out.println("-- " + message);

}

}

/**Main method*/

public static void main(String[] args)

{

HelloWorldTestClient client = new HelloWorldTestClient();

// Use the client object to call one of the Home interface wrappers

// above to create a Remote interface reference to the bean.

// If the return value is of the Remote interface type you can use it

// to access the remote interface methods. You can also just use

// the client object to call the Remote interface wrappers.

}

}

HelloWorldTestClient.java 的运行流程如下首先调用构造函数HelloWorldTestClient()

在构造函数中先是使用lookup()方法定位Home Interface 的位置获取Home Object

亦即helloWorldHome 然后调用create()方法该方法返回一个Remote 对象(hwr) 接下来

调用hwr 对象的getMessage()方法这其实是调用HelloWorld EJB 的商业方法getMessage()

getMessage()方法的返回值为Hello World 我们把返回值赋给一个字符串然后输出

客户端程序的运行方法如下

在JBuilder4 的主界面中左侧右键单击HelloWorldTestClient.java Run 运行输出

________

第2 章 Enterprise JavaBeans

如下所示

-- Initializing bean access.

-- Calling create()

-- Succeeded: create()

-- Execution time: 380 ms.

-- Return value from create(): Stub[repository_id=RMI ...

Hello World

-- Succeeded initializing bean access.

-- Execution time: 10660 ms.

读者也许已经发现了这个EJB 客户端程序的输出结果显示调用这个HelloWorld EJB

组件的商业方法(getMessage())花费了很长的时间一共是10660ms 亦即10.66 秒这并

非是EJB 组件的运行效率不佳而是因为这个EJB 组件的服务端(SmartAgent)和客户端

(JBuilder4)都在笔者的电脑上面运行与此同时笔者的电脑上还运行了一个数据库服务器

(MS SQL Server) 一个Web 服务器(IAS) 还有一个字处理软件(Word) 系统资源有限所

以运行速度才这么慢如果EJB Server 在别的电脑上运行EJB Client 在本地机上运行真

正实现了分布式处理的梦想那时候EJB 组件的运行效率还是相当高的闲话少说现在

转入正题由上面的运行结果可见HelloWorld EJB 已经成功发布成功运行了怎么样

是不是很简单呢?下面我们该把这个EJB 正式部署到商业服务器上去了我们首先选用的服

务器是IAS4.1

2.5.5 打包分发EJB 服务

在这一小节中将介绍如何把上面编写好的HelloWorld EJB 分发到IAS 服务器上作

为一项服务发布

启动IAS 服务器

第一步是启动IAS 服务器你可以从开始菜单启动IAS 服务器也可以进入IAS 的

bin 目录下面双击执行ias.exe 程序这样也可以运行IAS 服务器当出现了类似于下面

的字样那说明IAS 服务器已经成功了

Inprise Application Server 4.1.1

Copyright (c) 1999-2000 Inprise Corporation. All Rights Reserved.

Portions Copyright (c) 1997-1999 Sun Microsystems Inc. All Rights Reserved.

Server rainbow Started

运行EJB Deployment

在JBuilder4 的主运行界面中选择Tools EJB Deployment…. 出现了如图2.15 所示

的窗口

________

第一部分 JSP 技术与J2EE 技术

图2.15 JBuilder4 的IAS EJB Deployment Wizard 窗口

在图2.15 中有两个选项Quick 和Advanced Quick 选项适合于发布单个EJB 组件

而Advanced 选项适合于发布多个EJB 组件在这个例子中我们选择Quick 然后单击

Next 出现了如图2.16 所示的窗口

图2.16 JBuilder4 的IAS EJB Deployment Wizard 窗口

选择需要发布的EJB 组件

在这一步我们将选择需要发布的EJB 组件和EJB Container 如图2.16 所示

在图2.16 中Jar file 一栏用于填写你所希望发布的EJB 组件所在的jar 文件的路径

________

第2 章 Enterprise JavaBeans

你可以单击Browse 按钮修改它在Container 一栏列出了目前可用的EJB Container 缺

省状态下是ias://localhost/ejbcontainer 如果这一栏为空那么请确定IAS 服务器是否已经

运行了如果IAS 服务器不是出于运行状态那是没有办法分发EJB 服务的

设置停当后单击Next 出现如图2.17 所示的窗口

EJB 组件分发成功

如果你上面的步骤没有出什么漏子的话那么你将会看到如图2.17 所示的画面EJB

组件已经分发成功了在图2.17 中单击Finish 按钮关闭这个窗口回到JBuilder4 的主

界面再次运行EJB 的客户端程序(右键单击HelloWorldTestClient.java Run) 我们会看到

这次的运行结果和上面使用SmartAgent 作为EJB Container 得到的结果几乎没有不同如

果有不同那就是运行所耗费的时间不同

到此为止我们已经成功地开发了一个EJB 组件成功地测试了这个EJB 组件并且

成功地把它发布到一个EJB Container(IAS)中去了我们是否应该到此结束呢?不!事情还没

有结束呢我们应该选用一个更好的EJB Container 把EJB 组件发布到哪里去IAS 服务

器作为EJB Container 还不是最好的选择对于EJB Container BEA 的WebLogic IBM 的

WebSphere 才是更好的选择好了你现在可以关闭JBuilder4 进入下一小节的学习在

下一小节我们将介绍如何把EJB 组件分发到WebLogic 服务器上以WebLogic 服务器作

为EJB 组件的EJB Server/Container

图2.17 EJB 组件分发成功了

________

第一部分 JSP 技术与J2EE 技术

2.5.6 使用WebLogic 服务器分发EJB 服务

在本小节我们将介绍如何把使用JBuilder4 开发的EJB 组件分发到WebLogic 服务器

上笔者认为WebLogic 服务器是最好的商用J2EE 运行环境也是最好的EJB

Server/Container 这是有一定根据的例如WebLogic 支持最新的EJB 技术规范WebLogic

的市场占有率最高等我们之所以首先介绍如何把EJB 组件分发到IAS 服务器上面去那

是因为JBuilder4 和IAS 都是Borland 公司的产品利用JBuilder4 开发的EJB 组件最容易

发布到IAS 服务器中并不是IAS 服务器最适合做EJB Server/Container

现在让我们开始吧

安装WebLogic 服务器

第一步应该安装WebLogic 服务器笔者推荐使用WebLogic 5.1/6.0 你可以到

www.weblogic.com 去下载一个试用版然后配置好WebLogic 服务器使它支持JSP 关于

如何配置WebLogic 服务器的方法请参考本书的附录3

封装EJB 组件

第二步是把EJB 组件所用到的class 文件都打包到一个jar 文件中如果我们是使用

JBuilder4 开发的EJB 组件那么在编译EJB 工程的时候JBuilder4 会自动把所有相关的文

件都打包到一个jar 文件中这个jar 文件的名字就是工程的名字

转移jar 文件

把jar 文件从JBuilder4 的开发目录转移到WebLogic 服务器安装目录的myserver 文件

夹的根目录下面这样做的目的是为了便于管理EJB 组件如果略过这一步也可以

配置EJB Deployer

从开始菜单运行WebLogic 附带的EJB Deployer 如图2.18 所示

图2.18 EJB Deployer

________

第2 章 Enterprise JavaBeans

图2.18 显示了EJB Deployer 的主运行界面在左边的视图中单击Server 接着在右

边的窗口里单击Add 按钮右边的窗口将如图2.19 所示

图2.19 WebLogic 服务器的设置

我们需要在如图2.19 所示的窗口中设置WebLogic 服务器的信息前面4 项都是预先

设定的不需要改动第4 项System password 需要我们自己输入然后在Default 前面的

框中打上勾这样我们就把WebLogic 服务器设为缺省的EJB Container

接下来请选择Tools Preferences... 会出现如图2.20 所示的对话框我们需要在这个

对话框中指定ejbc.exe 程序的属性也就是指定Java 编译器javac.exe 程序所在的路径

然后单击OK 按钮关闭这个对话框回到EJB Deployer 的主界面

图2.20 WebLogic EJB Deployer Properties 对话框

编辑EJB 部署描述符

我们在EJB Deployer 的主运行界面中选择File Open... 打开在上文第二步中封装

好的jar 文件(在这个例子中这个jar 文件就是HelloWorld.jar 也就是我们在2.3 2.4 节

中开发的HelloWorld EJB 组件所对应的jar 文件) EJB Deployer 将出现如图2.21 所示的对

话框这时请你稍微耐心一点这可能需要花比较长的时间

________

第一部分 JSP 技术与J2EE 技术

图2.21 WebLogic EJB DeployTool 对话框

当EJB Deployer 把jar 文件完全载入以后如图2.21 所示的对话框将会消失同时在

EJB Deployer 主运行界面的视图区(左侧) 会出现此EJB 组件的树状视图我们可以把这

个树状视图一级一级展开然后在右侧的窗口编辑该EJB 组件的部署描述符

在左侧的视图区单击Files 节点右边的窗口将显示刚刚载入的jar 文件中都含有哪

些文件如图2.22 所示

图2.22 查看EJB jar 文件所包含的文件列表

实际上我们在JBuilder4 中开发HelloWorld EJB 组件时已经编辑了它的ejb-jar.xml

文件ejb-jar.xml 文件保存了EJB 组件部署描述符的大部分信息所以在EJB Deployer 中

我们不用对部署描述符做太多的修改(对部署描述符所做的任何改动都将修改ejb-jar.xml 文

件) 不过有一点我们必须指定HelloWorld EJB 组件所使用的JNDI 服务名EJB 组件的

客户端程序将凭借这个服务名定位EJB 的Home Interface 进而调用EJB 组件的商业方法

那么如何设定HelloWorld EJB组件在WebLogic 服务器中的JNDI 服务名呢?在EJB Deployer

________

第2 章 Enterprise JavaBeans

的主运行界面的左侧视图区展开Beans 节点点击HelloWorld 在EJB Deployer 主窗口

的右侧出现了如图2.23 所示的窗口选择Classes Tab 在Home JNDI name 一栏下面的

文本框中填入HelloWorld 这就是HelloWorld EJB 的JNDI 服务名现在我们可以保存

所做的工作了

图2.23 设定EJB 组件的JNDI 服务名

创建EJB Container

单击工具栏中的Generate container…按钮就是那一个类似于齿轮模样的按钮创建

HelloWorld EJB 组件的EJB Container 在这一步中EJB Deployer 将调用ejbc.exe 程序

产生该EJB 组件的stub 类(存根类)和skeleton 类(框架类) 也就是Home Object 类和EJB

Object 类EJB Deployer 还将调用javac.exe 程序把它们编译为class 文件并加入到前面的

jar 文件中在这一步中EJB Deployer 还会自动产生weblogic-ejb-jar.xml 文件并且也会

打包到jar 文件中EJB Deployer 在执行这一步的时候会消耗很多的系统资源要花大概

一分多钟的时间在此期间会出现如图2.24 所示的窗口并且还可能弹出几个MS-DOS

窗口这是EJB Deployer 在调用ejbc.exe 程序和javac.exe 程序读者不要去管它也不要

随便关闭这些MS-DOS 窗口它们会自动关闭的当创建HelloWorld EJB 的EJB Container

完成以后会出现如图2.25 所示的对话框单击Close 按钮把它关闭掉返回EJB Deployer

的主界面

我们虽然已经创建了HelloWorld EJB 的EJB Container 但是HelloWorld EJB 组件仍然

没有被发布到WebLogic 服务器中我们也可以利用EJB Deployer 临时把HelloWorld EJB

________

第一部分 JSP 技术与J2EE 技术

发布到WebLogic 服务器中这只需要单击Deploy…按钮就可以了但这只是临时的发布

如果WebLogic 服务器关闭了再次启动的时候它不会自动把HelloWorld EJB 组件载入

的如果我们希望WebLogic 服务器在启动的时候自动载入HelloWorld EJB 必须修改

WebLogic 服务器的配置文件

图2.24 Generator EJB Container

图2.25 创建EJB Object /HomeObject 成功

修改weblogic.properties 文件

这一步我们将修改weblogic.properties 文件使得WebLogic 服务器能够在启动的时

候自动把HelloWorld EJB 载入当作一个JNDI 服务发布如果WebLogic 服务器是处于运

行状态那么应该首先关闭WebLogic 服务器然后使用记事本打开WebLogic 安装目录(根

目录)下的weblogic.properties 文件找到下面的代码行

#weblogic.ejb.deploy=\

# D:/weblogic/myserver/ejb_basic_beanManaged.jar \

# D:/weblogic/myserver/ejb_basic_containerManaged.jar \

# D:/weblogic/myserver/ejb_basic_statefulSession.jar \

# D:/weblogic/myserver/ejb_basic_statelessSession.jar \

# D:/weblogic/myserver/ejb_extensions_finderEnumeration.jar \

# D:/weblogic/myserver/ejb_extensions_readMostly.jar \

# D:/weblogic/myserver/ejb_subclass.jar \

# D:/weblogic/myserver/jolt_ejb_bankapp.jar

然后在这些代码行的后面添加上下面的代码行(请注意/和\)

________

第2 章 Enterprise JavaBeans

weblogic.ejb.deploy=\

D:/weblogic/myserver/HelloWorld.jar \

在上面的代码中HelloWorld.jar 文件就是我们使用JBuilder4 所开发的HelloWorld EJB

的jar 文件而且这个文件已经从JBuilder4 的开发目录转移到WebLogic 的myserver 文件

夹下面还使用EJB Deployer 程序处理过保存好weblogic.properties 文件然后启动

WebLogic 服务器当WebLogic 服务器的运行窗口(wlserver)出现类似于下面的信息时那

么恭喜你这说明你已经成功地把HelloWorld EJB 组件发布到WebLogic 服务器中

WebLogic 服务器已经自动地把它载入当作一个JNDI 服务发布而且WebLogic 服务器

也成功启动了

星期四 三月 25 16:15:40 GMT+08:00 1999:<I> <EJB> 1 EJB jar files loaded containing 1 EJBs

星期四 三月 25 16:15:40 GMT+08:00 1999:<I> <EJB> 1 deployed 0 failed to deploy.

星期四 三月 25 16:15:41 GMT+08:00 1999:<I> <ZAC> ZAC ACLs initialized

星期四 三月 25 16:15:41 GMT+08:00 1999:<I> <ZAC> ZAC packages stored in local di

rectory exports

星期四 三月 25 16:15:41 GMT+08:00 1999:<I> <ListenThread> Listening on port: 7001

星期四 三月 25 16:15:41 GMT+08:00 1999:<A> <Posix Performance Pack> Could not in

itialize POSIX Performance Pack.

星期四 三月 25 16:15:41 GMT+08:00 1999:<E> <Performance Pack> Unable to load per

formance pack using Java I/O.

星期四 三月 25 16:15:42 GMT+08:00 1999:<I> <WebLogicServer> WebLogic Server star

ted

2.5.7 编写JSP 程序访问EJB 服务

既然HelloWorld EJB 组件已经成功地发布到WebLogic 服务器中那么我们怎么才能

够在JSP 程序中调用这个EJB 组件的商业方法呢?也就是如何编写一个基于JSP 程序的EJB

客户端呢?在上文中我们虽然已经实现了EJB 的客户端程序不过那是一个Java

Application 不能够用于JSP 程序中本书第一部分的主旨一方面在于介绍J2EE 应用的

开发技术另一方面则是这些J2EE 技术如何与JSP 技术结合起来开发功能强大的应用系

统因为J2EE 技术比较难掌握而利用JSP 技术开发J2EE 应用程序的客户端就相对容易

得多所以我们在第一方面花费了大量的笔墨而第二方面就介绍比较少这不是说第二

方面不重要实际上两方面都十分重要都不可偏颇 (毕竟我们开发出来的EJB 组件

主要还是在JSP 程序中调用) 只不过第一方面的知识实在太多太难而我们以前又很少

接触罢了

下面我们就来介绍该如何编写JSP 程序访问EJB 组件请看下面的程序清单2.14

程序清单2.14

<%--

File Name:ejb.jsp

Author:fancy

Date:2001.5.10

Note:ejb client

--%>

________

第一部分 JSP 技术与J2EE 技术

<%@ page import=" java.rmi.RemoteException"%>

<%@ page import=" java.util.Properties"%>

<%@ page import=" javax.ejb.CreateException"%>

<%@ page import=" javax.ejb.RemoveException"%>

<%@ page import=" javax.naming.Context"%>

<%@ page import=" javax.naming.InitialContext"%>

<%@ page import=" javax.naming.NamingException"%>

<%@ page import=" javax.rmi.PortableRemoteObject"%>

<%@ page import="helloworld.*" %>

<%

HelloWorldHome testHome = null;

HelloWorldRemote testRemote = null;

Context ctx = new InitialContext();

//look up jndi name

Object ref = ctx.lookup("t3://localhost:7001/HelloWorld");

//cast to Home interface

testHome = (HelloWorldHome) PortableRemoteObject.narrow(ref HelloWorldHome.class);

testRemote=testHome.create();

String author=testRemote.getMessage();

out.println(author);

%>

读者对照程序清单2.14 和程序清单2.13 不难发现这两个程序的流程几乎一样都是

查找Home Interface 获取Home Interface 以后创建Remote Interface 最后利用Remote

Interface 调用EJB 的商业方法把结果输出程序就差不多了实际上基于JSP 的EJB

客户端与基于Java Application 的EJB 客户端在本质原理上没有任何区别我们可以很容

易地把一个基于Java Application 的EJB 客户端改写为JSP 程序

在程序清单2.14 中读者需要注意两个问题

1 需要导入helloworld 包请看下面的代码

<%@ page import="helloworld.*" %>

我们在编写HelloWorld EJB 的时候所有的类都是属于helloworld 包的如果我们不

把这个包导入JSP 程序中那么WebLogic 的JSP 引擎将因为无法识别HelloWorldHome 接

口HelloWorldRemote 接口而报错helloworld 包其实就是HelloWorld.jar 文件中包含的那

些class 文件我们之所以把HelloWorld.jar 文件从JBuilder4 的开发目录转移到WebLogic

的myserver 文件夹下面其中一个很重要的原因就是便于JSP 引擎找到helloworld 包中所

定义的那些类/接口

2 请看下面的代码在定位Home 接口时必须使用这样的格式

Object ref = ctx.lookup("t3://localhost:7001/HelloWorld");

如果我们使用WebLogic 作为EJB Server 那么EJB 的JNDI 服务名会是这样的格式

________

第2 章 Enterprise JavaBeans

t3://host:port/JNDIName

如果我们使用IAS 作为EJB Server 那么EJB 的JNDI 服务名会是另外的格式如

ias://host/ejbcontainer/JNDIName

host 代表主机的名字可以是本地机也可以是远程服务器这没有任何限制port

是服务器的服务端口名对于WebLogic 服务器这个值一般是7001 JNDIName 就是JNDI

服务的名字我们可以通过编辑部署描述符的方式来指定它例如HelloWorld

程序清单2.17 的运行效果如图2.26 所示

图2.26 ejb.jsp

2.6 本 章 小 结

本章重点介绍的内容有EJB 的体系结构EJB 开发环境的配置如何使用JBuilder

4.0+IAS 4.1 开发会话EJB 发布EJB 服务这主要针对IAS 服务器和WebLogic 服务器

编写EJB 服务的客户端调用EJB 组件的商业方法不知道读者都掌握了没有应该说

本章是本书最难明白的章节之一光看一次是很难完全领会的只有按照书中的方法一

步一步自己去试验才有可能掌握好EJB 技术

本章所介绍的EJB 技术还比较基本还没有涉及到实体EJB 的开发方法在下一章中

我们将接着介绍如何开发CMP 模式BMP 模式的实体EJB

________

第3 章 EJB 技术进阶

在上一章中我们已经介绍了EJB 的基础知识还详细描述了Session EJB 的开发

运行和测试技术在本章我们将继续介绍EJB 的开发技术但是重点将转移到实体EJB

的开发技术上本章需要重点掌握的内容有

CMP 类型的实体EJB 的开发

BMP 类型的实体EJB 的开发

利用BMP 类型的实体EJB 封装数据源

3.1 实体EJB 的开发技术之一CMP EJB

在上一章中我们已经提到过EJB 技术规范支持两种类型的EJB 对象它们分别是

Session EJB(会话EJB)和Entity EJB(实体EJB) 第3 章所开发的EJB 组件属于会话EJB

Session EJB(会话EJB)是短暂存在的对象它同样运行在服务器端并执行一些应用

逻辑处理它的实例对象由客户端应用程序建立并仅能够被创建它的应用程序所使用

其保存的数据需要开发者编写程序来管理Session EJB 支持事务属性但是当系统因为

某种特殊的不可预见的原因崩溃或者关闭后Session EJB 的状态数据不会再被系统恢复

Session EJB 的这种特性十分类似于Page Scope 类型的JavaBeans 组件对象

Entity EJB(实体EJB)是持久运行的EJB 对象由某个客户端应用程序创建但是可以

被其他对象或者其他的应用程序调用与Session EJB 不同Entity EJB 必须在建立时制定

一个惟一的标识并提供相应的机制允许客户应用程序根据Entity EJB 标识来定位Entity

EJB 的实例对象多个用户可以并发访问Entity EJB 事务之间的协调工作由EJB Container

来完成读者应当注意Session EJB 只能够被创建它的应用程序所调用所以不存在事务

协调的问题Entity EJB 支持事务处理当系统关闭时也可以恢复关闭以前的状态包括

EJB 对象所包含的数据在内EJB 规范中定义了两种管理Entity EJB 的持久性模型即Beans

Managed Persistence(Beans 自管理方式简称为BMP)及Container Managed Persistence (容

器代管理方式简称为CMP) BMP 管理方式是由EJB 对象自己来管理持久性它需要EJB

开发者来编写数据库或应用程序的处理逻辑并加入到EJB 对象类(EJBObject Class)的

ejbCreate() ejbRemove() ejbFind() ejbLoad()和ejbStore() Finder 等方法中CMP 管理

方式是将EJB 持久性管理交给EJB Container 来完成开发者不用过多操心开发者一般要

在EJB 对象的Deployment Descriptor(部署描述符)中的Container Managed 属性中指定EJB

实例对象的持久性作用域当使用CMP 模式时开发者不需要知道Entity EJB 如何存取数

据源开发者也很少需要参与复杂烦琐的编码工作不过CMP 模式的实体EJB 功能比

较单一使用起来不灵活扩展起来比较难而采用BMP 模式那就需要开发者事必躬亲

对每一个细小的问题都必须考虑周到但是BMP 模式的实体EJB 开发起来很灵活开发

者有很大的自由度可以定制复杂的处理逻辑扩展起来也比较容易在实际的开发过程

________

第3 章 EJB 技术进阶

中如果需要使用实体EJB 那么比较单一固定的任务可以交给CMP 模式的实体EJB

完成而灵活复杂的系统最好还是采用BMP 模式的实体EJB 读者需要注意的是在会

话EJB 中没有CMP 与BMP 模式之分只有Stateless 与Stateful 之分

下面我们首先介绍CMP Entity EJB 的开发方法

3.1.1 CMP EJB 简介

CMP 模式的实体EJB 非常简单因为有很多的细节问题我们都不需要去关心EJB

Container 会自动实现它们例如EJB 类中定义的ejbCreate() ejbRemove() ejbPassivate()

ejbLoad() ejbStore() getXXX() setXXX() 等方法还有Home 接口中声明的

findByPrimaryKey() findAll()等方法我们只需要简单地声明它们或者简单地返回具

体的实现要依靠EJB Container 还有一个更为重要的问题那就是DataSource 在CMP

模式下面我们根本就不需要理会DataSource 在哪里DataSource 如何与实体EJB 绑定在

一起实体EJB 如何存取数据如何Load 如何Store 如何Create 还有事务的问题

还有持久性的问题还有并发执行的问题等等所有的问题都归EJB Container 管理开发

者不用费半点心思我们只要把Home Interface Remote Interface EJB 类部署描述符做

出来利用EJB Deploy 工具就能够自动生成完备的可以完成上述任务的EJB Container

怎么样是不是很轻松很简单呢?下面我们就来看看如何利用JBuilder4 开发一个CMP 模

式的实体EJB

3.1.2 创建EJB 工程

我们还是按照上一章的方法首先利用JBuilder4 建立一个EJB 的框架架构然后在

这个基础之上进行第二次开发在开发CMP Entity EJB 之前我们同样要做好配置开发环

境的工作具体来说就是配置好IAS 服务器和JBuilder4 关于配置的方法读者可以参

考第2 章的介绍因为我们在上一章已经做好了配置工作所以在这里就把这一步给掠过

了不过读者最好还是从头检查一遍开发环境开发过程中出现的问题多半是由于开发环

境没有配置好而造成的

配置好开发环境以后我们还有一步额外的工作要做那就是配置JDBC 数据源实

体EJB 的主要作用就是映射数据库的结构并提供一个间接访问数据库中数据的方法所

以我们在开发实体EJB 之前必须首先指定JDBC 数据源

那么如何指定JDBC 数据源呢?请按照下面的步骤去做

1 创建一个数据库我们所使用的数据库系统是MS SQL Server 7.0 至于数据库

的结构请参考附录4 ODBC 数据源的名称为test

2 启动JBuilder 选择Tools JDBC Explorer 将出现如图3.1 所示的窗口

3 在JDBC Explorer 中选择File New…. 出现一个新窗口在这个窗口中我

们指定JDBC Driver 为sun.jdbc.odbc.JdbcOdbcDriver 读者可以改为其他的JDBC Driver

JDBC URL为jdbc:odbc:test(因为ODBC 数据源的名称为test) 读者也可以改为其他的JDBC

URL 结果如图3.2 所示

4 在图3.2 中单击OK 出现了如图3.3 所示的窗口我们输入的用户名为sa

密码为空然后单击OK 按钮那么JDBC Explorer 程序将试图连接到数据库中

________

第一部分 JSP 技术与J2EE 技术

图3.1 JDBC Explorer 的窗口

图3.2 创建新的JDBC DataSource

图3.3 输入数据库的用户名和密码

5 在图3.3 中单击OK 按钮之后如果JDBC Explorer 连接数据库系统成功那

么JDBC Explorer 的主窗口将如图3.4 所示我们应该逐个展看窗口左侧的视图明了数据

库的字段结构同时也可以在右侧的窗口中输入SQL 语句并执行它改变数据库的结构

或者更新里面的数据

________

第3 章 EJB 技术进阶

图3.4 利用JDBC Explorer 查看数据库的结构

6 关闭JDBC Explorer 这时候程序提示你是否保存所做的改变你应该选择保存

要不然你所做的一切工作都白费了又得从头再来

配置好JDBC DataSource 以后我们的下一步就是创建一个实体EJB 工程读者请跟

着下面的步骤一步一步尝试一定会成功的

7 在JBuilder4 的主运行界面中选择File New… Enterprise Tab Empty EJB

Group JBuilder4 会自动创建一个新的Project 我们把这个Project 的名字指定为JDBCTest

如图3.5 所示

图3.5 JBuilder4 的Project Wizard

________

第一部分 JSP 技术与J2EE 技术

8 创建好JDBCTest Project 以后Empty EJB Group Wizard 会自动运行我们指定

这个EJB Group 的名字为JDBCTest 包含此EJB 组件的jar 文件名为JDBCTest.jar 如图

3.6 所示

图3.6 JBuilder4 的Empty EJB Group Wizard

9 创建好EJB Group 以后下面该创建一个Entity EJB 了在JBuilder 的主运行界

面中选择File New… Enterprise Tab EJB Entity Bean Modeler 然后单击OK 按钮

如图3.7 所示

图3.7 创建EJB Entity Bean Modeler

10 在图3.7 的窗口中单击OK按钮之后出现如图3.8 所示的对话框确保Available

EJB groups 下拉列表中选的是JDBCTest.ejbgrp 然后单击Next 按钮

________

第3 章 EJB 技术进阶

图3.8 JBuilder4 的EJB Entity Bean Modeler Wizard

11 接着上一步将出现如图3.9 所示的对话框

图3.9 设定JDBC DataSource

在图3.9 中我们需要点击Choose Existing Connection…按钮选择已经存在的JDBC

数据源也就是一开始就设定的那一个JDBC 数据源jdbc:odbc:test 然后在Username

________

第一部分 JSP 技术与J2EE 技术

处填入sa 让Password 空着JNDI name 保持DataSource 不变单击Next 按钮进入下

一步

12 在这一步中设定Entity EJB 需要映射的Table JBuilder4 会根据Table 的字段

名字自动产生setXXX()方法和getXXX()方法jdbc:odbc:test 数据源所对应的数据库一共

有4 个用户表分别是goods 表tbuser 表tborder 表dtproperties 表我们选定goods

表作为Entity EJB 映射的Table 方法是在左侧的窗口中选定需要映射的Table 然后单

击> 按钮那么这个Table 的名字就会移到右边的窗口中这样就可以了如图3.10

所示

图3.10 选定Entity EJB 所映射的Table

13 在图3.10 的对话框中单击Next 按钮进入一个新的对话框不要做任何改

动再次单击Next 按钮将显示如图3.11 所示的对话框在图3.11 的对话框中我们将

设定Entity EJB 的Primary Key 亦即主键在Entity EJB 的Home Interface 中声明了

findPrimaryKey()方法利用这个方法可以获取特定的Remote 对象我们所说的主键

其实就是Table 的某一个或者某几个特定的字段但是这些字段的值必须是唯一的亦即

在数据库的记录中不能够有相同的值如果某一个字段被设为主键那么JBuilder4 只会

为它自动产生getXXX()方法而不会产生setXXX()方法因为主键是标识Entity EJB 的唯

一的标志当然不能够被改动而且主键的值必须是唯一的不被设定为主键的字段既可

以有setXXX()方法也可以有getXXX()方法其实Entity EJB 的主键与数据库的主键有一

定的相似之处Entity EJB 就相当于记录集中的一个行纪录

在图3.11 的对话框中我们选定字段id 作为此Entity EJB 的主键这只需要在id 字

段前面的那一个小框中打勾即可选中id 字段以后单击Next 按钮进入下一步

14 在图3.11 中单击Next 按钮以后会进入如图3.12 所示的对话框JBuilder4

________

第3 章 EJB 技术进阶

根据Table 的名字(goods)自动生成这个Entity EJB 的Home Interface Remote Interface EJB

Class JNDI 服务的名字我们可以修改这些名字把它们改成希望的值在这个例子中

Bean 的名字为Goods JNDI 服务名为Goods Home Interface 的名字为jdbctest.GoodsHome

Remote Interface 的名字为jdbctest.GoodsRemote EJB Class 的名字为GoodsBean 主键的

类型为int 也就是goods Table 的id 字段一切都设置好以后清单击Next 按钮进入下

一步

图3.11 选定Entity EJB 的主键

图3.12 设定Entity EJB 的Home Remote Interface 和EJB Class 的名字

________

第一部分 JSP 技术与J2EE 技术

15 在图3.12 的对话框中单击Next 按钮将进入如图3.13 所示的对话框在该

对话框中我们将设定Goods EJB 的模式是CMP 还是BMP

图3.13 设定Entity EJB 的模式

如图3.13 所示我们选中了Container managed persistence 还选中了Generate findAll()

method in home interface 这样JBuilder4 将为我们自动生成一个Entity EJB 的运行框架这

个Entity EJB 的模式是CMP 亦即容器管理模式JBuilder4 还将在Home Interface 中声明

一个findAll()方法利用这个方法可以获取在EJB Container 中所有可用的同类Remote

Interface

单击Finish 按钮这样就算搭建起一个Entity EJB 的框架了实际上我们不用做任

何修改只需要编译这个Project Goods EJB 就能够成功运行了

下面我们将简要地分析一下究竟JBuilder4 都给我们生成了哪些代码Entity EJB 和

Session EJB 都有些什么不同

3.1.3 Home Interface 和Remote Interface

这一小节中我们分析Goods EJB 的Home Interface 和Remote Interface 首先是Home

Interface 请看程序清单3.1

程序清单3.1

//File Name: GoodsHome.java

//Author:fancy

//Date:2001.5

//Note:the Home Interface

package jdbctest;

________

第3 章 EJB 技术进阶

import java.rmi.*;

import javax.ejb.*;

import java.util.*;

public interface GoodsHome extends EJBHome

{

public GoodsRemote create(String goodsname String goodstype String comment

Double price Double priceoff int id) throws RemoteException CreateException;

public GoodsRemote create(int id) throws RemoteException CreateException;

public GoodsRemote findByPrimaryKey(int primaryKey) throws RemoteException

FinderException;

public Collection findAll() throws RemoteException FinderException;

}

在程序清单3.1 中定义了Goods EJB 的Home Interface Home Interface 必须继承

EJBHome 接口这一点无论是Session EJB 还是Entity EJB 都不能够违背

GoodsHome 接口中声明了4 个方法第一个create()方法的返回值是一个GoodsRemote

对象调用这个方法就相当于往数据库中插入了一个新的行纪录第二个create()方法也

返回一个GoodsRemote 对象也相当于往数据库中插入了一个新的行纪录不过这行纪录

只有主键被赋值其余的字段都是空值findByPrimaryKey()方法可以根据指定的主键的值

返回特定的GoodsRemote 对象这个GoodsRemote 对象代表了某个特定的行纪录最后一

个方法是findAll()方法findAll()方法的返回值是Collection 对象这个Collection 对象包

含的是EJB Container 中所有可用的同类的Remote 接口对象相当于一个完整的记录集

Entity EJB 所映射的Table 有多少行那么此Collention 对象就包含有多少个元素EJB

Container 中就有多少个可用的同类的Remote 接口对象利用findPrimaryKey()方法和

findAll()方法就可以实现检索数据库的功能了

下面我们来看看Remote Interface 的代码请看程序清单3.2

程序清单3.2

//File Name: GoodsRemote.java

//Author:fancy

//Date:2001.5

//Note:the Remote Interface

package jdbctest;

import java.rmi.*;

import javax.ejb.*;

public interface GoodsRemote extends EJBObject

{

String getGoodsname() throws RemoteException;

________

第一部分 JSP 技术与J2EE 技术

void setGoodsname(String goodsname) throws RemoteException;

String getGoodstype() throws RemoteException;

void setGoodstype(String goodstype) throws RemoteException;

String getComment() throws RemoteException;

void setComment(String comment) throws RemoteException;

Double getPrice() throws RemoteException;

void setPrice(Double price) throws RemoteException;

Double getPriceoff() throws RemoteException;

void setPriceoff(Double priceoff) throws RemoteException;

int getId() throws RemoteException;

}

Remote Interface 必须继承自EJBObject 接口在程序清单3.2 中定义了一套getXXX()

方法和setXXX()方法这些getXXX()方法和setXXX()方法都是JBuilder4 根据goods Table

的各个字段的名字和JDBC 数据类型自动生成的用于获取与设定Entity EJB 的属性值

实际上是在存取行纪录的各个字段的值关于这些方法的用法读者可以参考程序清单3.6

3.1.4 EJB 类

本小节中我们分析EJB 类的实现代码请看程序清单3.3

程序清单3.3

//File Name: GoodsBean.java

//Author:fancy

//Date:2001.5

//Note:the Remote Interface

package jdbctest;

import java.rmi.*;

import javax.ejb.*;

public class GoodsBean implements EntityBean

{

EntityContext entityContext;

public String goodsname;

public String goodstype;

public String comment;

public Double price;

public Double priceoff;

public int id;

public int ejbCreate(String goodsname String goodstype String comment Double

price Double priceoff int id) throws CreateException

{

this.goodsname = goodsname;

this.goodstype = goodstype;

this.comment = comment;

________

第3 章 EJB 技术进阶

this.price = price;

this.priceoff = priceoff;

this.id = id;

return 0;

}

public int ejbCreate(int id) throws CreateException

{

return ejbCreate(null null null null null id);

}

public void ejbPostCreate(String goodsname String goodstype String comment

Double price Double priceoff int id) throws CreateException

{

}

public void ejbPostCreate(int id) throws CreateException

{

ejbPostCreate(null null null null null id);

}

public void ejbRemove() throws RemoveException

{

}

public void ejbActivate()

{

}

public void ejbPassivate()

{

}

public void ejbLoad()

{

}

public void ejbStore()

{

}

public void setEntityContext(EntityContext entityContext)

{

this.entityContext = entityContext;

}

public void unsetEntityContext()

{

entityContext = null;

}

public String getGoodsname()

{

return goodsname;

}

public void setGoodsname(String goodsname)

{

________

第一部分 JSP 技术与J2EE 技术

this.goodsname = goodsname;

}

public String getGoodstype()

{

return goodstype;

}

public void setGoodstype(String goodstype)

{

this.goodstype = goodstype;

}

public String getComment()

{

return comment;

}

public void setComment(String comment)

{

this.comment = comment;

}

public Double getPrice()

{

return price;

}

public void setPrice(Double price)

{

this.price = price;

}

public Double getPriceoff()

{

return priceoff;

}

public void setPriceoff(Double priceoff)

{

this.priceoff = priceoff;

}

public int getId()

{

return id;

}

}

Entity EJB 的EJB Class 必须继承自EntityBean 接口而Session EJB 的EJB Class 必须

继承自SessionBean 接口在程序清单3.3 中读者不难发现ejbLoad() ejbStore()

ejbRemove() ejbActivate() ejbPassivate()等方法的方法体都是空的ejbCreate()方法的方法

体也只是简单地把结果返回具体实现的程序逻辑被隐藏起来了这就是所谓的CMP 模式

EJB Container 把一切的事情都包办了我们只要会调用这些方法就可以了不需要知道它

们是如何被实现的

________

第3 章 EJB 技术进阶

在程序清单3.3 中还定义了很多setXXX()方法与getXXX()方法这些就是所谓的商

业方法亦即在Remote Interface 中声明的那些方法我们可以利用Home Interface 创建一

个Remote Interface 然后再利用Remote Inteerface 通过stub 类和skelton 类调用这些商业方

法具体的步骤读者可以参考程序清单3.6

3.1.5 部署描述符

下面我们就来介绍Entity EJB 的部署描述符在第2 章的时候我们已经提到过使用

JBuilder4 开发的EJB 组件的部署描述符包含在两个XML 文件中这两个XML 文件分别

是ejb-jar.xml 文件和ejb-inprise.xml 文件我们先来看看Goods EJB 的ejb-jar.xml 文件请

看程序清单3.4 (ejb-jar.xml)

程序清单3.4(ejb-jar.xml)

<?xml version="1.0" encoding="GBK"?>

<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems Inc.//DTD Enterprise JavaBeans

1.1//EN' 'http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd'>

<ejb-jar>

<enterprise-beans>

<entity>

<ejb-name>Goods</ejb-name>

<home>jdbctest.GoodsHome</home>

<remote>jdbctest.GoodsRemote</remote>

<ejb-class>jdbctest.GoodsBean</ejb-class>

<persistence-type>Container</persistence-type>

<prim-key-class>int</prim-key-class>

<reentrant>False</reentrant>

<cmp-field>

<field-name>goodsname</field-name>

</cmp-field>

<cmp-field>

<field-name>goodstype</field-name>

</cmp-field>

<cmp-field>

<field-name>comment</field-name>

</cmp-field>

<cmp-field>

<field-name>price</field-name>

</cmp-field>

<cmp-field>

<field-name>priceoff</field-name>

</cmp-field>

<cmp-field>

________

第一部分 JSP 技术与J2EE 技术

<field-name>id</field-name>

</cmp-field>

<primkey-field>id</primkey-field>

<resource-ref>

<res-ref-name>jdbc/DataSource</res-ref-name>

<res-type>javax.sql.DataSource</res-type>

<res-auth>Container</res-auth>

</resource-ref>

</entity>

</enterprise-beans>

<assembly-descriptor>

<container-transaction>

<method>

<ejb-name>Goods</ejb-name>

<method-name>*</method-name>

</method>

<trans-attribute>Required</trans-attribute>

</container-transaction>

</assembly-descriptor>

</ejb-jar>

在ejb-jar.xml 文件中定义了Goods EJB 的具体信息包括Remote Interface Home

Interface 的名字管理模式(CMP) 主键的数据类型还定义了Goods EJB 所映射的goods

Table 的字段名下面的这段代码定义了Goods EJB 所使用的JDBC 数据源的名字

<resource-ref>

<res-ref-name>jdbc/DataSource</res-ref-name>

<res-type>javax.sql.DataSource</res-type>

<res-auth>Container</res-auth>

</resource-ref>

下面我们再来看看ejb-inprise.xml 请看程序清单3.5(ejb-inprise.xml)

程序清单3.5(ejb-inprise.xml)

<?xml version="1.0" encoding="GBK"?>

<!DOCTYPE inprise-specific PUBLIC '-//Inprise Corporation//DTD Enterprise JavaBeans

1.1//EN' 'http://www.borland.com/devsupport/appserver/dtds/ejb-inprise.dtd'>

<inprise-specific>

<enterprise-beans>

<entity>

<ejb-name>Goods</ejb-name>

<bean-home-name>Goods</bean-home-name>

<resource-ref>

________

第3 章 EJB 技术进阶

<res-ref-name>jdbc/DataSource</res-ref-name>

<jndi-name>DataSource</jndi-name>

</resource-ref>

<cmp-info>

<database-map>

<table>goods</table>

</database-map>

<finder>

<method-signature>findAll()</method-signature>

<where-clause />

<load-state>True</load-state>

</finder>

</cmp-info>

</entity>

</enterprise-beans>

<datasource-definitions>

<datasource>

<jndi-name>DataSource</jndi-name>

<url>jdbc:odbc:test</url>

<username>sa</username>

<password></password>

<driver-class-name>sun.jdbc.odbc.JdbcOdbcDriver</driver-class-name>

</datasource>

</datasource-definitions>

</inprise-specific>

在程序清单3.5 中主要定义的是Goods EJB 所使用的JDBC 数据源的信息例如用

户名是什么密码是什么JDBC URL 地址是什么等等还定义了Goods EJB 所映射的Table

为goods ejb-inprise.xml 是Borland 公司特有的XML 文件如果是利用别的公司的产品开

发EJB 组件那么这个XML 文件的名字可能有所不同例如jboss.xml 就是JBoss 所指定

的EJB 组件的部署描述符weblogic-ejb-jar.xml 就是WebLogic 服务器所需要的EJB 组件的

部署描述符而ejb-jar.xml 文件是EJB 规范规定的必须要有的XML 文件无论使用哪种

工具开发EJB 组件都必须包含这个XML 文件

EJB 容器(EJB Container)

2.2.3 EJB Container

EJB 容器(EJB Container)是一个管理一个或多个EJB 类/实例的平台它通过在规范中

定义的接口使EJB 类访问所需的服务EJB 容器厂商也可以在容器或服务器中提供额外服

务的接口现在还没有EJB 服务器和EJB 容器间接口的规范因为目前EJB 容器通常由

EJB 服务器来提供它们一般都是同一个厂家的产品所以一旦EJB 服务器和EJB 容器之

间的接口标准化了厂商就可能提供可以在任何兼容的EJB 服务器上运行的EJB 容器

Home 接口支持所有定位创建删除EJB 远程对象的方法Home 对象(Home Object)

是Home 接口的实现EJB 类开发者必须自己定义Home 接口EJB 容器厂商应该实现从

Home 接口中产生Remote 对象的方法远程接口Remote Interface 封装了EJB 对象中的

商业方法EJB Object 实现了远程接口并且客户端通过它访问EJB 实例的商业方法EJB

类开发者必须自己定义远程接口EJB 容器开发商必须提供产生相应的EJB Object 的方法

________

第一部分 JSP 技术与J2EE 技术

客户端程序不能得到EJB 实例的引用只能得到它的EJB Object 实例的引用当客户端应

用程序调用某个方法EJB Remote 对象接受请求并把它传给EJB Object 同时提供进程中

必要的包装功能客户端应用程序通过调用Home 对象的方法来定位创建删除EJB 类

的实例(其实是远程接口对象) 通过Remote 对象来调用EJB Object 中的商业方法客户端

应用程序可以用Java 语言来编写通过Java RMI 技术来访问EJB Container 中的Home 对

象和Remote 或者用其他语言编程并通过CORBA/IIOP 技术访问已经部署的服务器端组

在理解EJB 规范时对于EJB 容器这个术语我们并不应从字面上简单地把它理解为

一个类而是应该理解为一层代替EJB Object 访问EJB Server 并调用相应系统服务的接口

EJB 容器开发商应该提供一套完成这些功能的工具和接口这些工具和编程接口可以运行

在EJB Server 上

上面提到的系统服务包括

持久性管理对于实体EJB 而言

实现创建和查找服务的Home 对象的可用性

可通过JNDI 技术访问的Home 对象对于客户端程序的可视性

正确地创建初始化和删除EJB Object/Remote 对象

保证商业方法能够正确地运行在事务上下文中

实现某一基本的安全服务

从Home Interface 和EJB Object 上产生stub 类(存根类)和skeleton 类(框架类) 以

实现客户端与服务端的交互

EJB Object 必须与EJB 容器通讯来确定调用商业方法的事务上下文确定以后EJB

Object 在调用商业方法以前建立事务上下文重要的是EJB Object 和EJB 容器的协同工作

来实现EJB 容器所需的事务EJB 容器厂商提供二者的实现但对二者的功能分割却是自

由的

目前EJB 容器厂商都提供专门的Deploy 工具来读取Home Interafce 并产生作为EJB

容器类的Home Object 在这种情况下EJB 容器厂商对于每个EJB Object 类使用不同的EJB

容器类EJB 容器厂商还可以使用其它的实现策略如一个EJB 容器类实现多个Home 接

口即产生多个Home Object 唯一的必要条件是EJB 容器厂商必须使客户端程序能通过

JNDI 技术访问Home Object 进而访问Remote Interface 再由Remote Interface 访问EJB

Object 客户端应用程序的开发者和EJB 组件开发者都不需关心EJB 容器和Home Object

的实现细节

2.2.4 Home Interface

客户端的应用程序调用EJB 组件的方法时必须首先通过调用EJB 组件的Home 接口

(Home Interface)的方法创建它(指的是EJB 组件)的远程实例(Remote Object) Home 接口包

含了一个或多个用来创建EJB 远程实例的create()方法这个Home 接口的实现不是由Bean

来实现而是通过一些称为Home Object 的类来实现的这些所谓的Home Object 类一般

不是由开发者编写的而是由开发工具自动生成的(EJB 容器生产厂家提供这些开发工具)

当然了如果你希望自己编写Home Object 类这没有什么特别的问题不过你需要透彻

________

第2 章 Enterprise JavaBeans

了解不同的EJB Container 是如何实现Home 接口的不同的EJB Container 的实现方法往

往大相径庭到了下面我们还会提到这个问题所以我们建议读者使用专门的工具自动产

生Home Object 类而不是自己编写它们

Home Object 类的实例在EJB Server 中实例化形成EJB Container 使得客户端的应

用程序可以访问并且定位它们

一个Home Object 的引用被存放在JNDI 服务中客户端的应用程序能够通过JNDI 技

术访问它EJB Server 一般需要提供某种名字空间的实现或者是使用外部的名字空间

在这两种情况下客户端应用程序都必须知道名字空间的位置以及JNDI 的上下文类例如

一个客户端的JSP 程序可能接收名字空间和JNDI 上下文类作为JSP 程序的参数除了提

供名字空间的位置和JNDI 上下文类名(Context) 客户端应用程序也必须知道如何在名字空

间树状结构中定位Home Object 这些必须在客户端程序启动时提供当EJB 部署者把EJB

组件发布到EJB 服务器中时他必须以参数形式指定名字树例如ejb/Test 客户端程序必

须获得这个完整的路径名来定位并获得Test Home Object 的引用这不是说客户端应用程

序通过JNDI 获得EJB 容器而是客户端应用程序使用JNDI 技术查找(lookup)Home 接口的

实现Home 接口的实现由某个特殊的EJB Container 来提供(通过Home Object) 但这是

EJB 容器厂商的细节EJB 组件开发者和客户端程序的开发者可以忽略它并且应该忽略它

EJB 开发者在定义EJB Object 中的ejbCreate()方法的同时必须在Home 接口中声明与

其相应的create()方法实体EJB 可以包含Finder 方法以使得客户端程序能够定位已有的实

体EJB 对象这是因为会话EJB 只能够被创建它的客户端使用而且一旦客户端对其的调

用结束那么会话EJB 会被EJB Server 自动注销所以会话EJB 不支持finder()方法实

体EJB 就不同了一旦被创建就一直存在于EJB Server 中它可以被多个客户端程序同

时使用所以才支持finder()方法我们在编写Home 接口的时候必须注意到Home 接口

是通过继承javax.ejb.EJBHome 接口来定义的EJBHome 接口包含如下的方法

public void remove(Handle handle) throws java.rmi.RemoteException RemoveException;

public void remove(java.lang.Object primaryKey) throws java.rmi.RemoteException

RemoveException;

public EJBMetaData getEJBMetaData() throws java.rmi.RemoteException;

public HomeHandle getHomeHandle() throws java.rmi.RemoteException;

一个EJB 的Home 接口可以像下面的样子

程序清单2.1

//File Name:TestHome.java

//Date:2001.4.30

//Author:fancy

//Note:create a Home Interface

import java.rmi.*;

import javax.ejb.*;

public interface TestHome extends EJBHome

{

public TestRemote create() throws RemoteException CreateException;

}

________

第一部分 JSP 技术与J2EE 技术

程序清单2.1 是一个最简单的Home Interface 一个Home Interface 必须扩展EJBHome

接口而且必须声明create()方法这个create()方法的返回类型必须是Remote Interface 的

实例create()方法需要抛出RemoteException 和CreateException 这两个异常否则编译不

会通过

程序还需要导入java.rmi 包与javax.ejb 包的支持

在本小节结束之时我们再次强调一遍EJB 容器开发商负责提供实现Home 接口的

Home Object 类或者提供实现的方法与工具因为只有EJB 容器开发商才能实现存储EJB

的库的编码

2.2.5 Remote Interface

EJB 开发者已经创建了一个客户端应用程序可以访问create()方法的Home Interface

在Home Interface 中定义的 create()方法在相应的Bean 中都必须有一个ejbCreate()方法与之

对应同样EJB 开发者必须创建封装了客户端应用程序能够访问的商业方法的Remote

Interface

因为所有的客户端程序调用的方法都必须通过Remote Interface 因此实现这个接口的

是EJB Object 而不是Home Object Remote Interface 中列出的方法名必须和实现Bean 的方

法名相同

我们在编写Remote Interafce 的时候必须注意Remote Interface 必须扩展

javax.ejb.EJBObject 接口EJBObject 接口定义的方法如下所示:

public EJBHome getEJBHome() throws java.rmi.RemoteException;

public java.lang.Object getPrimaryKey() throws java.rmi.RemoteException;

public void remove() throws java.rmi.RemoteException RemoveException;

public Handle getHandle() throws java.rmi.RemoteException;

public boolean isIdentical(EJBObject obj) throws java.rmi.RemoteException;

以下是一个Remote Interface 的例子

程序清单2.2

//File Name:TestRemote.java

//Author:fancy

//Date:2001.4.30

//Note:create a Remote Interface

import java.rmi.*;

import javax.ejb.*;

public interface TestRemote extends EJBObject

{

public java.lang.String getAuthor() throws RemoteException;

}

所有在Remote Interface 中声明的方法都必须抛出一个RemoteException 异常因为EJB

规范要求客户端的stub 类必须是Java RMI 技术兼容的但这并不意味着排除了用其它的传

输方式的stub/skeleton 实现如CORBA/IIOP

Remote Interface 继承javax.ejb.EJBObject 接口又声明了额外的方法在上面的例子

________

第2 章 Enterprise JavaBeans

中我们声明了getAuthor()方法客户端的应用程序就可以调用这个方法

2.3 EJB 体系结构(二)

2.3.1 EJB Object

EJB Object(EJB 对象)是网络上的可视对象(对于Remote Interface 而言是可视的) 它包

含了stub 和skeleton 作为EJB 类的代理EJB 组件的Remote Interface 继承了

javax.ejb.EJBObject 接口而EJB Object 则实现了这个Remote Interface 使得EJB 类与

Remote Interface 之间搭起了一座桥梁对于每个EJB 都有一个定制的EJB Object EJB

Object 隔离了EJB 类使得EJB 类在网络中是不可见的不过EJB Object 提供了stub 和

skeleton Remote Interface 通过这两条路径与EJB 类相互通信所谓的EJB 类就是真正

实现了商业逻辑商业方法的类它有两种类型一种是Session EJB(会话EJB) 一种是

Entity EJB(实体EJB) 在前面我们对EJB Object 与EJB 类不加区分统一称为EJB Object

或者EJB 对象但是由此开始我们就需要把它们严格区分开来打一个不十分贴切的比

方EJB 类相当于一个人那么EJB Object 就相当于人身上的衣服

实现Remote 接口的EJB Object 其实是一个RMI 服务器对象注意EJB 类本身不是一

个Remote Object 它在网络上是不可视的当EJB 容器实例化了这个EJB Object 后EJB

容器会接着初始化EJB 类的实例引用使得EJB Object 能够处理Remote Interface 传送过

来的请求代理EJB 类所实现的商业方法的调用

EJB Object 是由提供EJB 容器EJB Server 的厂商实现的厂商的实现维护了EJB

Object 实例和EJB 实例的一对一关系因为Remote Interface 包含了EJBObject 接口的方法

所以EJB 类不必显式地实现这个接口虽然它提供了Remote Interface 所列出的商业方法的

实现因为EJB Object 必须正式地实现EJB 的Remote Interface EJB 容器在部署EJB 时会

自动产生EJB Object 的源代码并编译为二进制文件这些产生的EJB Object 源代码实现

了Remote Interface 典型的EJB Object 有一个独特的类名作为EJB Object 和EJB 类的联

2.3.2 Session EJB

EnterpriseBean 是EJB 开发者编写的提供应用程序功能的类简称为EJB 类在上文

中我们已经有所提及EnterpriseBean 是EJB 组件模型中具体实现商业逻辑商业方法的部

分EnterpriseBean 有两种会话EJB 与实体EJB EJB 开发者可以选择创建会话EJB 或实

体EJB 通过实现不同的接口声明不同的部署描述符来加以区分

对于会话EJB 必须实现javax.ejb.SessionBean 接口对于实体EJB 必须实现

javax.ejb.EntityBean 客户端应用程序不会也不能够直接访问EnterpriseBean 中的任何方法

客户端应用程序通过Remote Interface/EJB Object 间接调用EnterpriseBean 中的方法EJB

Object 就像一个代理服务器一样在把调用通过EJB Object 传递时容器开发商通过包装

EJB Object 的编码插入自己的功能这称为方法插入方法插入的一个例子是为每个方法

调用创建一个新的事务上下文当方法返回到EJB Object 时提交(commit)或回滚(rollback)

事务

________

第一部分 JSP 技术与J2EE 技术

当使用EJB 容器厂商的发布工具在分发部署EJB 时发布工具会产生EJB 组件的EJB

Object 的一个stub 和skeleton 实际上发布工具并不创建EnterpriseBean 本身的stub 和

skeleton 也并不需要创建因为EnterpriseBean 不会通过网络直接被访问到EJB Object

才是真正的网络对象

EJB 容器也可以调用EnterpriseBean 中的某个方法例如EJB 容器保证当一个

EnterpriseBean 实例生成后Home Object 中的create()的任何参数会传递到EnterpriseBean

相应的ejbCreate()方法EnterpriseBean 还有其它的接口和要求然而会话EJB 和实体

EJB 的要求是不同的下面我们就来介绍会话EJB(Session EJB) 下一小节再介绍实体

EJB(Entity EJB)

会话EJB 是一种通过Home Interface 创建并对客户端连接专有的EnterpriseBean 会话

EJB 的实例一般不与其它客户端共享这允许会话EJB 维护客户端的状态会话EJB 的一

个例子是购物车众多顾客可以同时购物往他们自己的购物车中添加商品而不是向一

个公共的购货车中加私人的货物

我们可以通过定义一个实现javax.ejb.SessionBean 接口的类来创建一个会话EJB 该接

口(SessionBean 接口)的定义如下

public void setSessionContext(SessionContext ctx) throws EJBException

java.rmi.RemoteException;

public void ejbRemove() throws EJBException java.rmi.RemoteException;

public void ejbActivate() throws EJBException java.rmi.RemoteException;

public void ejbPassivate() throws EJBException java.rmi.RemoteException;

javax.ejb.EnterpriseBean 是一个空接口是会话EJB 和实体EJB 的超类下面是一个

简单的会话EJB 的例子

程序清单2.3

//File Name:TestBean.java

//Author:fancy

//Date:2001.5.1

//Note:to create a session EJB

import java.rmi.*;

import javax.ejb.*;

public class TestBean implements SessionBean

{

private SessionContext sessionContext;

public void ejbCreate()

{

}

public void ejbRemove()

{

}

public void ejbActivate()

{

________

第2 章 Enterprise JavaBeans

}

public void ejbPassivate()

{

}

public void setSessionContext(SessionContext context)

{

sessionContext = context;

}

public String getAuthor()

{

return "Rainbow";

}

}

在上面的例子中geAuthor()方法是EJB 开发者自己定义的商业逻辑

会话EJB 的交换(切换)

EJB 容器开发商可以实现把会话EJB 的实例从主存移到二级存储中的交换机制这可

以增加在特定的一段时间内实例化的会话EJB 的总数EJB 容器维护一个会话EJB 实例的

时间期限当某个会话EJB 的不活动状态时间达到这个阙值EJB 容器就把这个会话EJB

实例拷贝到二级存储中并从主存中删除EJB 容器可以使用任何机制来实现会话EJB 的持

久性存储最常用的方式是通过EJB 的串行化这个交换机制与Windows 操作系统的内存

交换机制有一定的相似之处

活化和钝化

为了支持EJB 容器厂商提供会话EJB 的交换EJB 规范定义了钝化把会话EJB 从

主存转移到二级存储的过程活化把会话EJB 恢复到主存中去的过程在

javax.ejb.SessionBean 接口中声明的ejbActivate()和ejbPassivate()方法允许EJB 容器通知

已经被活化的会话EJB 的实例它将要被EJB 容器钝化EJB 开发者可以用这些方法释放

或者恢复处于钝化状态的会话EJB 所占有的值引用和系统资源一个可能的例子是数据

库连接作为有限的系统资源不能被处于钝化状态的会话EJB 所使用

有了这些方法就使得不必再使用transient 事实上使用transient 可能是不安全的

因为串行化机制自动地把transient 的值设为null 或0 而通过ejbActivate()和ejbPassivate()

方法显式地设置这些参数更好一些依靠Java 的串性化机制把transient 的值设成null 也是

不可移植的因为会话EJB 有可能被部署在不使用Java 的串性化机制获得持久性的EJB

容器中这时该行为会发生改变如果EJB 容器不提供实现EJB 交换的机制那么这些方

法将永远不会被调用

当客户端程序调用EJB 的商业方法时钝化的会话EJB 被自动激活当EJB Object

收到方法调用的请求时它唤醒EJB 容器所需要活化的会话EJB 当活化完成时EJB Object

代理对会话EJB 的方法调用如果会话EJB 参与一个事务那么它不能被钝化把会话

EJB 放在主存中更有效率因为事务通常在很短的时间内完成如果会话EJB 没有钝化前

________

第一部分 JSP 技术与J2EE 技术

必须释放或活化前必须重置的状态那么这些方法(指的是ejbActivate()和ejbPassivate()方

法)可置空在大多数情况下EJB 开发者不必在这些方法中做任何事

会话EJB 的状态管理

会话EJB 的部署描述符必须声明该EJB 是有状态或者是无状态的一个无状态

(Stateless)的会话EJB 是在方法调用间不维护任何状态信息的EJB 通常会话EJB 的优点

是代替客户端维护状态然而让会话EJB 无状态也有一个好处无状态EJB 不能被钝化

因为它不维护状态所以没有需要保存的信息EJB 容器可以删除会话EJB 的实例客户

端应用程序永远不会知道无状态EJB 的删除过程客户端的引用是通过EJB Object 如果

客户端应用程序稍后又调用了一个商业方法则EJB Object 通知EJB 容器再实例化一个新

的会话EJB 实例因为没有状态所以也没有信息需要恢复

无状态EJB 可以在不同的客户端应用程序间共享只是在某一时刻只能有一个客户端

执行一个方法(独占状态) 因为在方法调用间没有需要维护的状态所以客户端应用程序

可使用任何无状态会话EJB 的实例这使得EJB 容器可以维护一个较小的可用EJB 的缓冲

池以节省主存因为无状态EJB 在方法调用间不能维护状态因此从技术上讲在Home

Interface 的create()方法不应有参数在创建EJB 时向EJB 传递参数意味着在ejbCreate()返

回时需要维护EJB 的状态而且EJB 实例一旦删除经由EJB Object 调用商业方法的结

果使得EJB 容器必须重新创建一个无状态的EJB 这时在最开始创建EJB 时所用的参数就

不存在了EJB 容器开发厂商的部署分发工具应该能检查Home Interface 如果是无状态对

话的EJB 应该保证其不包含带参数的create()方法

2.3.3 Entity EJB

实体EJB 的角色

实体EJB 用来代表底层的对象最常见的是用实体EJB 代表关系型数据库中的数据

一个简单的实体EJB 可以定义成代表数据库表的一个记录也就是每一个实例代表一个特

殊的记录更复杂的实体EJB 可以代表数据库表之间的关联视图在实体EJB 中还可以考

虑包含厂商的增强功能如对象-关系映射的集成

通常用实体类代表一个数据库表比代表多个相关联的表更简单且更有效反过来可以

轻易地向实体类的定义中增加关联这样可以最大地复用cache 并减小旧数据的表现

实体EJB 和会话EJB 的比较

看起来会话EJB 好象没什么用处尤其对于数据驱动的应用程序当然事实并不是这

样因为实体EJB 譬如说代表底层数据库的一行纪录则实体EJB 实例和数据库记录

间就是一对一的关系因为多个客户端程序必须访问底层记录这意味着不同于会话EJB

客户端程序必须共享实体EJB(事实上这个功能也可以使用会话EJB 来完成不过效率不

高消耗系统资源过多有多少个客户端程序在运行就需要多少个会话EJB 在服务端运

行而使用实体EJB 就好多了只需要一个实体EJB 在运行即可) 因为实体EJB 是共享

的所以实体EJB 不允许保存每个客户端的信息会话EJB 允许保存客户端的状态信息

客户端应用程序和会话EJB 实例间是一对一的实体EJB 允许保存记录的信息实体EJB

________

第2 章 Enterprise JavaBeans

的实例和记录间是一对一的一个理想的情况是客户端通过会话EJB 连接服务器然后会

话EJB 通过实体EJB 访问数据库这使得既可以保存客户端的信息又可以保存数据库记录

的信息

没有会话EJB 应用程序开发者客户端开发者就必须理解EJB 类的事务要求并

使用客户端的事务划分来提供事务控制EJB 的主要好处就是应用开发者不需知道EJB 类

的事务需求一个会话EJB 可以代表一个商业操作进行事务控制不需要在客户端程序

中进行事务划分

EntityBean 接口

实体Bean 类必须继承javax.ejb.EntityBean 接口下面我们应该来看看EntityBean 接口

所定义的方法

public void setEntityContext(EntityContext ctx) throws EJBException java.rmi.Remote

Exception

public void unsetEntityContext() throws EJBException java.rmi.RemoteException

public void ejbRemove() throws RemoveException EJBException java.rmi.Remote

Exception

public void ejbActivate() throws EJBException java.rmi.RemoteException

public void ejbPassivate() throws EJBException java.rmi.RemoteException

public void ejbLoad() throws EJBException java.rmi.RemoteException

public void ejbStore() throws EJBException java.rmi.RemoteException

Finder 方法

通过Home Interface 或者Remote Interface 创建和删除EJB 的实例对实体EJB 和会

话EJB 来说有不同的含义对会话EJB 来说删除意味着从EJB 容器中删除不能够再次

使用并且其状态信息也丢失了(也许没有状态信息) 对于实体EJB 删除意味着底层数据

库记录被删除了因此一般不把删除作为实体EJB 生命周期的一部分

创建一个实体EJB 意味着一个记录被插进数据库中与删除操作类似创建操作通常

也不作为实体EJB 生命周期的一部分客户端访问实体EJB 需要先找到它除了create()

方法以外一个实体EJB 的Home Interface 通常还有Finder 方法(指findByPrimaryKey()方

法) 客户端需要根据应用程序的限制来识别一个特殊的数据库记录亦即识别一个实体

EJB 例如程序清单2.4

程序清单2.4

//File Name:TestEntityEJBHome.java

//Author:fancy

//Date:2001.5.1

//Note:create a EntityBean’Home Interface

import java.rmi.*;

import javax.ejb.*;

public interface TestEntityEJBHome extends EJBHome

{

________

第一部分 JSP 技术与J2EE 技术

public TestEntityEJBRemote create() throws RemoteException

CreateException;

public TestEntityEJBRemote findByPrimaryKey(String primKey)

throws RemoteException FinderException;

}

当客户端程序调用Remote Object 的任何方法时EJB 容器会把调用传递到实体EJB

的相应方法中一个简单的Entity EJB 代码实例如程序清单2.5

程序清单2.5

//File Name:TestEntityEJB.java

//Author:fancy

//Date:2001.5.1

//Note:create a EntityBean Class

import java.rmi.*;

import javax.ejb.*;

public class TestEntityEJB implements EntityBean

{

EntityContext entityContext;

public String ejbCreate() throws CreateException

{

/**@todo: Implement this method*/

return null;

}

public void ejbPostCreate() throws CreateException

{

}

public void ejbRemove() throws RemoveException

{

}

public String ejbFindByPrimaryKey(String primKey)

{

//@todo: Implement this method

return null;

}

public void ejbActivate()

{

}

public void ejbPassivate()

{

}

public void ejbLoad()

{

}

public void ejbStore()

________

第2 章 Enterprise JavaBeans

{

}

public void setEntityContext(EntityContext entityContext)

{

this.entityContext = entityContext;

}

public void unsetEntityContext()

{

entityContext = null;

}

public String getAuthor()

{

return "fancy";

}

}

一个较好的方法是把Finder 方法当成数据库的SELECT 语句而动态SQL 参数相当

于方法的参数请注意Home Interface 中的Finder 方法向客户端返回一个EJB 的远程接口

对象(请参考程序清单2.4) 而实体EJB 中的Finder 方法向EJB 容器返回一个唯一的标识符

(请参考程序清单2.5) 称为主键(Primary Key) EJB 容器用这个主键实例化一个选定的EJB

Object 不论如何实现Finder 方法EJB 容器都用这个主键代表这个选定的记录(亦即选定

的实体EJB) 由实体EJB 类来决定如何用这唯一的标识符来代表记录

可能由一个Finder 方法得到满足SELECT 语句条件的多个记录这种情况下实体EJB

的Finder 方法将返回一个主键的枚举类型Home Interface 的Finder 方法返回值就是EJB

远程接口对象的枚举类型

主键

主键这个词有可能被曲解把它理解为唯一的标识符更恰当些当实体EJB 代表一个

数据库记录时主键可能是该记录的组合键对于每个实体EJB 的实例都有一个相应的

EJB Object 当一个EJB Object 与一个实体EJB 实例对应时该EJB 实例的主键保存在EJB

Object 中这时就说该实体EJB 的实例有一个标识符当客户端程序调用Home Object 的

Finder 方法时EJB 容器会用没有标识符的实体EJB 的实例来执行这个请求EJB 容器可

能为此维持一个甚至多个匿名的实例不论如何实现Finder 方法都必须向EJB 容器返回

底层数据的主键如数据库的记录如果多个记录满足条件那么就返回多个主键当EJB

容器得到主键后它会用该主键初始化一个EJB Object EJB 容器也可以初始化一个与EJB

Object 关联的实体EJB 的实例因为底层记录的标识符在EJB Object 中保存着因此在EJB

实例中没有状态因此EJB 容器可以在EJB Object 上调用商业方法时再实例化EJB 以

节省内存资源

当Finder 方法向EJB 容器返回主键时EJB 容器首先会检查该主键的EJB Object 是否

已经存在如果该主键的EJB Object 已经存在那么EJB容器不会创建一个新的EJB Object

而是向客户端返回这个已存在的EJB Object 的远程接口对象这样就保证了每个记录只有

________

第一部分 JSP 技术与J2EE 技术

一个EJB Object 的实例所有的客户端程序共享同一个EJB Object

主键只是在EJB 类中唯一地标识实体EJB 的实例EJB 容器负责保证其范围应该明

确Finder 方法只是从数据库中取出数据的主键而不包含其它的数据项也可能调用Finder

方法后不产生任何实体EJB 的实例只产生包含该主键的EJB Object 当客户端调用EJB

Object 的方法时再产生并导入实体EJB 的实例

客户端能在任何时候获得实体EJB 的主键并且以后可以使用该主键通过Home

Interface 重建对实体EJB 远程接口的引用主键类的类型在部署描述符中指定EJB 开发

者可以用任何类型来表示主键唯一的要求是类必须实现serializable(串行化) 因为该主键

可能在客户和服务器之间进行传递

实体EJB 的内外存交换

实体EJB 也有所谓的活化和钝化过程与会话EJB 类似然而不在事务过程中的实

体EJB 是无状态的;也可以说其状态总是和底层的数据同步的如果我们像钝化会话EJB

那样钝化实体EJB 则当钝化无状态实体EJB 时只会删除它但是因为EJB 容器调用Finder

方法需要匿名的实体EJB EJB 容器可能为此把不活动的实体EJB 钝化到一个私有内存池

中一旦从EJB Object 中删除了实体EJB 则同时也删除了标识符主键关联

当客户端程序调用没有相关的实体EJB 的EJB Object 的商业方法时EJB 容器就可能

用这个私有内存池重新分配实体EJB 注意这个内存池中的EJB 没有标识可以被任何EJB

Object 重用EJB 容器可以不维护任何有EJB Object 的实体EJB 除非有一个商业方法在

通过EJB Object 被调用如果实体EJB 在事务中运行则需保持其与EJB Object 的关联

自管理的持久性

实体EJB 的管理有两种模式一种是自管理模式另一种是容器管理模式因为实体

EJB 代表底层的数据所以我们需要把数据从数据库中取出然后放在EJB 中当EJB 容器

第一次把一个实体EJB的实例与EJB Object 关联时它就开始了一个事务并调用了这个EJB

的ejbLoad()方法在这个方法中开发者必须实现从数据库中取出正确的数据并把它放在

Bean 中当EJB 容器要提交一个事务时它首先调用EJB 的ejbStrore()方法这个方法负

责向数据库中回写数据我们称之为自管理持久性因为实体EJB 类方法中的代码提供了

这种同步

当ejbLoad()方法完成时EJB 有可能与底层数据库不一致商业方法的调用触发了与

EJB Object 关联的EJB 实例的分配在事务中执行的ejbLoad()方法必须在部署描述符中声

明根据接收到的方法调用请求EJB Object 和EJB 容器一起建立一个事务上下文EJB

容器分配与EJB Object 关联的EJB 实例并调用EJB 实例的ejbLoad()方法这个方法现在运

行在事务上下文中这个事务上下文传递给数据库根据部署描述符中指定的孤立性级别

这个事务锁定数据库中被访问的数据

只要事务上下文活动数据库中的数据就一直保持锁定状态当客户端或EJB 容器提

交事务时EJB 容器首先调用bean 的ejbStore()方法把EJB 中的数据回写到数据库中

相应的数据库记录在ejbLoad()和ejbStore()间保持锁定这个模式保证了EJB 和数据库间的

同步其间可以进行不同的商业方法调用而且ejbLoad()和ejbStore()明确地区分了事务

________

第2 章 Enterprise JavaBeans

边界事务中可以进行任何商业方法调用事务的持续时间由部署描述符决定也可能由

客户端决定

容器管理的持久性

如果部署描述符声明实体EJB 使用容器管理持久性则我们不需要自己调用ejbLoad()

和ejbStore()来访问数据库EJB 容器会自动把数据从数据库中导入到EJB 中然后调用

EJB 的ejbLoad()方法完成从数据库中接收数据

同样地EJB 容器调用实体EJB 的ejbStore()方法来完成把数据回写到数据库中这些

方法实际上没有执行任何数据库操作当EJB 容器开发商用复杂的工具来提供容器管理持

久性时如自动产生能进行对象-关系映射的实体EJB 类EJB2.0 规范规定了厂商必须提

供的容器管理实体持久性的最小需求集

2.3.4 部署描述符

部署描述符可以指定EJB 的一个public 域来实现与数据库列简单映射EJB 容器使用

EJB 部署描述符读出这个EJB 的public 域并写到相应的列或从数据库列中读出数据写到

public 域中EJB 容器管理的持久性对EJB 开发者来说是非常好的服务且不需对象-关系

影射等其他复杂的机制开发者会发现它比EJB 自管理的持久性更有效率

EJB 开发中两个主要的角色是EJB 开发者和EJB 部署者EJB 有很多属性为开发者所

不能预知如数据库的网络地址使用的数据库驱动程序等等部署描述符作为由EJB 开

发者定义的特性表由EJB 部署者添入正确的值EJB 部署描述符有标准的格式(一般是一

个XML 文件) 在开发和部署环境中是可移植的甚至在不同EJB 平台间也是可移植的

除了为开发EJB 和部署EJB 的协同提供一个标准的属性单部署描述符也应包含EJB

应如何执行有关事务和安全的细节信息一些如访问控制链ACL 等属性也应该由EJB

部署者来调整以保证适当的用户能在运行时使用EJB 其它属性如事务信息有可能

完全由EJB 开发者事先指定因为一般由EJB 开发者创建数据库访问代码并熟知EJB 的

方法应如何运行事务部署描述符是一个标准的Java 类我们需要创建一个部署描述符的

实例导入数据然后串行化这个串行化的部署描述符放在一个jar 文件中并和EJB 的

其他部分一起送到部署环境EJB 部署者利用某种部署工具读取这个串行化的部署描述符

可能修改一些属性值然后使用这个修改后的部署描述符来分发部署EJB

部署一个EJB 时分配对应的部署描述符然后初始化串行化再将其与其它EJB

类一起放入到一个jar 文件中不同厂商在编辑部署描述符时可能有不同的方式例如一

个厂商可能使用文本方式而另一厂商可能提供图形工具但最后结果的部署描述符是一

个标准的格式并且在不同平台间是可移植的

为了包装一个EJB 该EJB 的类接口和串行化的部署描述符放在一个jar 文件中

这个jar 文件必须含有一个manifest 文件manifest 文件提供jar 文件中所有文件的完整描

述例如

Manifest-Version: 1.0

Name: testbean/_TestRemote_Stub.class

Name: testbean/TestHomeHelper.class

________

第一部分 JSP 技术与J2EE 技术

Name: testbean/TestHomeOperations.class

Name: testbean/_TestHome_Stub.class

Name: testbean/TestHomeHolder.class

Name: testbean/TestRemotePOA.class

Name: testbean/TestRemote.class

Name: META-INF/ejb-jar.xml

Name: testbean/TestRemoteOperations.class

Name: META-INF/ejb-inprise.xml

Name: testbean/TestHomePOAInvokeHandler.class

Name: testbean/TestHome.class

Name: testbean/TestRemoteHelper.class

Name: testbean/TestRemotePOAInvokeHandler.class

Name: testbean/TestBean.class

Name: testbean/TestRemoteHolder.class

Name: testbean/TestHomePOA.class

开发者不必关心EJB jar 文件的创建EJB 容器的开发商一般会提供一个工具来帮助开

发者创建部署描述符然后把所有必需的文件打包进一个jar 文件中

2.4 如何开发EJB(一)

2.4.1 EJB 开发工具简介

由本小节开始我们将介绍如何开发EJB 如何把EJB 技术与JSP 技术结合起来首

先我们需要了解一些关于EJB 开发工具的知识

目前市场上流行的EJB Server/Container 有BEA 公司的WebLogic IBM 公司的

WebSphere Borland 公司的Inprise Application Server Allaire 公司的JRun Oracle 公司的

Oracle 8i/9i 前面4 个都是Web 服务器Oracle8i/9i 则是数据库服务器它们都支持

EJB1.0/1.1 技术规范其中WebLogic 6.0 服务器支持EJB 2.0 规范在这几种EJB

Server/Container 中我们建议读者选用WebLogic 作为Web 服务器端的EJB Server 以Oracle

8i/9i 作为数据库服务端的EJB Server

市场上流行的EJB 开发工具有Borland 公司的JBuilder 3.5/4.0 IBM 公司的VisualAge

3.0/3.5 Oracle 公司的JDeveloper 3.2 Allaire 公司的JRun Studio Sun 公司的Forte 2.0

Sybase 公司的PowerJ 这里没有什么好与坏之分我们建议读者选用的开发工具最好和选

用的EJB Server 相匹配例如如果你选用了Borland 公司的IAS 服务器做为EJB Server 那

么你最好选用Borland JBuilder 4.0 作为EJB的开发工具如果你选用IBM公司的WebSphere

作为EJB Server 那么你最好选用VisualAge 3.0/3.5 作为EJB 开发工具当然了也不是

非得如此不可只是这样做至少不会产生EJB 与Container/Server 不相匹配的问题

在本书中我们选用Borland 公司的JBuilder 4.0 Enterprise Edition 作为EJB 的开发工

具以Borland 公司的Inprise Application Server 4.1.1(简称为IAS)服务器的Smart Agent 作

为测试EJB 性能的临时EJB Server 以BEA 公司的WebLogic5.1 服务器作为最终分发部署

________

第2 章 Enterprise JavaBeans

EJB 组件的EJB Server/Container 我们还选用WebLogic 5.1 作为JSP 服务器利用它来测

试EJB 的客户端(指基于Web 模式的客户端基于Application 模式的客户端程序在JBuilder4

中直接测试)

2.4.2 JBuilder 4.0+IAS 4.1 的开发环境配置

在本节中我们将介绍如何配置JBuilder 4.0 的开发环境使得它可以开发EJB 组件

首先你必须安装JBuilder 4.0 Foundation Edition 然后再安装JBuilder 4.0 Enterprise

Edition 这没有什么好说的

启动JBuilder4 依次选择File New… Enterprise Tab 我们会看到与EJB CORBA

工程有关的图标都处于不可用的状态这说明暂时我们还无法创建一个EJB 工程

安装IAS 4.11 服务器可以安装在任意目下面我们建议读者把IAS 安装到JBuilder4

的程序目录下面

安装IAS4.11 完毕后我们在JBuilder4 中选择Tools Configure Libraries… 出现

如图2.1 所示对话框

图2.1 JBuilder4 的Configure Libraries 对话框

单击New… 把IAS 服务器所含的库文件(指JBuilder4\IAS\lib 目录下面的jar 文件)都

添加到JBuilder4 的库列表中作为一个新库库名为IAS 如图2.2 所示

单击OK OK 完成Configure Libraries 的工作

在JBuilder4 中单击Tools Enterprise Setup…. CORBA Tab 如图2.3 所示

选择VisiBroker Edit…. 出现了Edit Configuration 对话框如图2.4 所示

在Path for ORB tools 下面的文本框中设定IAS 服务器安装目录的bin 文件夹的路径

然后把Library for projects 下面的文本框设为IAS 这样使得当前的工程可以使用IAS 库

IAS 库含有EJB API 一切就绪后单击OK

接着上面一步选择Application Server Tab IAS 4.1 Tab 如图2.5 所示

________

第一部分 JSP 技术与J2EE 技术

图2.2 JBuilder4 的New Library Wizard 对话框

图2.3 JBuilder4 的Enterprise Setup 对话框

________

第2 章 Enterprise JavaBeans

图2.4 JBuilder4 的Enterprise Setup 对话框

图2.5 JBuilder4 的Enterprise Setup 对话框

________

第一部分 JSP 技术与J2EE 技术

在Enable integration 前面的框中打勾在IAS installation directory 下面的文本框中

设定IAS 服务器的安装目录单击OK OK 回到JBuilder4 的主窗口

关闭JBuilder 4 再次启动JBuilder 4 配置开发环境的工作就告一段落了

2.4.3 创建EJB 工程

由本小节开始我们将一步一步的引导读者创建一个完整的EJB 项目

在JBuilder4 中依次选择File New… Enterprise Tab Empty EJB Group 如图2.6

所示

图2.6 JBuilder4 的Object Gallery 对话框

单击OK 出现下一个对话框设定Project name 为HelloWorld 单击Next 又出现

新对话框

接着上一步不要做任何改动单击Next 出现新对话框输入Title Author Company

Project Description 等注释信息单击Finish

接着上一步这时候出现了一个名为Empty EJB Group Wizard 的对话框在Name 一

栏中输入HelloWorld 如图2.7 所示

单击OK 就回到了JBuilder4 的主界面

在JBuilder4 中依次选择File New… Enterprise Tag Enterprise JavaBeans OK

出现了Enterprise JavaBeans Wizard 的对话框在第一个对话框中什么也不要改动

Available EJB groups 下面的下拉列表框的值应该缺省为HelloWorld.ejbgrp 单击Next 出

现了下一个对话框

接上一步在Class Information Class Name 一栏中改变缺省值EnterpriseBean1 为

HelloWorld 其它地方不要改动(package 为helloworld Base Class 为java.lang.Object options

选中Stateless session bean 这表明将要创建一个无状态的Session EJB) 单击Next 出现

下一个对话框如图2.8 所示

________

第2 章 Enterprise JavaBeans

图2.7 JBuilder4 的Empty EJB Group Wizard 对话框

图2.8 JBuilder4 的Enterprise JavaBeans 对话框

在图2.8 中读者不难看出EJB Home Interface 的名字为HelloWorldHome Remote

________

第一部分 JSP 技术与J2EE 技术

Interface 的名字为HelloWorldRemote Session EJB 的名字为HelloWorld 读者可以任意改

变这些接口/类的名称只是不要相同就可以了不过我们建议读者不要做任何改动

接着上一步单击Finish 回到JBuilder4 的主开发界面

在JBuilder4 中依次选择Project Make Project 编译项目应该没有任何错误如

果出现错误请检查是否把IAS 库添加到当前Project 的CLASSPATH 中去了(使用菜单

Project Project Properties…进行设置) 还需要按照上面的步骤一步步检查看看哪一步

做错了改正过来再编译一遍

如果项目编译成功那说明EJB 已经基本开发成形了

2.4.4 开发EJB 类

EJB 类是实现商业逻辑商业方法的地方在2.4.3 小节中我们已经利用JBuilder4

提供的Wizard 自动生成了一个最简单的EJB 自然也包括了一个最简单的EJB 类我们

需要再此基础上进行二次开发添加我们所需要的方法代码

在JBuilder4 的主窗口中左侧双击HelloWorld.java 然后就可以在右侧的文件编辑

区编辑HelloWorld.java 的源代码HelloWorld.java 的初始源代码如程序清单2.6

程序清单2.6

package helloworld;

import java.rmi.*;

import javax.ejb.*;

/**

* Title:HelloWorld.java

* Description:create session ejb class

* Copyright: Copyright (c) 1999

* Company:Peking University

* @author:fancy

* @version 1.0

*/

public class HelloWorld implements SessionBean

{

private SessionContext sessionContext;

public void ejbCreate()

{

}

public void ejbRemove()

{

}

public void ejbActivate()

{

________

第2 章 Enterprise JavaBeans

}

public void ejbPassivate()

{

}

public void setSessionContext(SessionContext context)

{

sessionContext = context;

}

}

在HelloWorld.java 文件中添加如下的代码段

public String getMessage()

{

return “Hello World”;

}

方法getMessage()也就是所谓的商业方法商业逻辑当然了我们只是举一个最最

简单的例子实际上EJB 类的代码十分复杂绝不会如此简单但是我们可以在此基础上

继续开发添加更多的方法与代码完成更复杂的功能

在JBuilder4 主窗口的右下方单击HelloWorld Tab Bean Tab Methods 在JBuilder4

主窗口右侧中间出现了HelloWorld类的方法列表把滚动条移动到最下方找到getMessage()

方法getMessage()方法前面有一个小方框我们在这个小方框上打勾然后保存所有的文

件如图2.9 所示

图2.9 使商业方法(getMessage())与EJB 的Remote 接口建立关联

在JBuilder4 中依次选择Project Make Project 编译器提示下面的错误

Specification compliance error in "HelloWorld.jar": Please run Verify in the EJB DD Editor

________

第一部分 JSP 技术与J2EE 技术

"HelloWorld.ejbgrp": Stateless Session Bean: "HelloWorld" The Java-IDL reverse mapping

does not support Java type names which have a package name and a type name which are the

same ignoring case: helloworld.HelloWorld

"HelloWorld.ejbgrp": Stateless Session Bean: "HelloWorld" There must be a

transaction-attribute associated with the method: public abstract java.lang.String

helloworld.HelloWorldRemote.getMessage() throws java.rmi.RemoteException

不要管这些错误请看下一小节

2.4.5 开发Remote Interface

根据上文的描述Remote Interface 是客户端程序与EJB 实例之间的桥梁客户端正是

通过Remote 接口的实例对象由EJB Object 代理对EJB 商业逻辑商业方法的调用Remote

Interface 实际上是对EJB 商业方法的一个封装客户端程序可以调用的方法都在Remote

Interface 中得到了声明真正的实现却是在EJB 类/EJB Object 中

在JBuilder4 主界面中左侧双击HelloWorldRemote.java 在右侧的编辑区就可以编

辑HelloWorldRemote.java 文件了HelloWorldRemote.java 的初始代码如程序清单2.7

程序清单2.7

package helloworld;

import java.rmi.*;

import javax.ejb.*;

/**

* Title:HelloWorldRemote.java

* Description:create a Remote Interface class

* Copyright: Copyright (c) 1999

* Company:Peking University

* @author:fancy

* @version 1.0

*/

public interface HelloWorldRemote extends EJBObject

{

public java.lang.String getMessage() throws RemoteException;

}

在上面的代码中可以看到EJB 的Remote Interface 声明了getMessage()方法我们在

HelloWorld 类中已经实现了这个商业方法

我们一般不直接编辑Remote Interface 的代码建议读者采取的方法是在HelloWorld

类中加入商业方法的实现代码然后单击Bean Methods 在商业方法名字前的小方框中

打勾保存所有文件那么这些商业方法就会自动在Remote Interface 中声明了

________

第2 章 Enterprise JavaBeans

2.4.6 开发Home Interface

接下来我们该看看Home Interface 如何开发了在上文中我们已经提到过Home

Interface 十分重要客户端的应用程序必须定位Home 接口然后通过它的create()方法来

创建EJB 的远程接口对象

我们使用JBuilder4 的EJB Wizard 自动创建的Home Interface 的初始代码如程序清单

2.8

程序清单2.8

package helloworld;

import java.rmi.*;

import javax.ejb.*;

/**

* Title:HelloWorldHome.java

* Description:to create a Home Interface class

* Copyright: Copyright (c) 1999

* Company:Peking University

* @author:fancy

* @version 1.0

*/

public interface HelloWorldHome extends EJBHome

{

public HelloWorldRemote create() throws RemoteException CreateException;

}

如果我们开发的是会话EJB 那么一般不需要对Home Interface 的源代码进行修改

2.4.7 编辑部署文件

下面我们介绍如何利用JBuilder4 编辑EJB 的部署描述符

在JBuilder4 窗口的左侧双击HelloWorld.ejbgrp 左侧窗口被分成了上下两部分下

面的部分为EJB Deployment Descriptor 在下面的部分中展开HelloWorld 如图2.10 所示

单击Container Transactions JBuilder4 主界面的右侧出现了部署描述符的图形编辑界

面单击Add 保存全部文件如图2.11 所示

然后单击图2.11 上方打勾的图标检验部署描述符的语法没有任何错误这样部署

描述符就算开发完成了在图2.11 中我们可以通过单击EJB DD Editor 来编辑部署描述

符通过单击EJB DD Source 面板来查看部署描述符的源文件JBuilder4 为我们创建了两

个XML 文件作为部署描述符分别是ejb-jar.xml 和ejb-inprise.xml 这两个文件的代码如

程序清单2.9 及2.10

程序清单2.9(ejb-jar.xml)

<?xml version="1.0" encoding="GBK"?>

________

第一部分 JSP 技术与J2EE 技术

<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems Inc.//DTD Enterprise JavaBeans

1.1//EN' 'http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd'>

<ejb-jar>

<enterprise-beans>

<session>

<ejb-name>HelloWorld</ejb-name>

<home>helloworld.HelloWorldHome</home>

<remote>helloworld.HelloWorldRemote</remote>

<ejb-class>helloworld.HelloWorld</ejb-class>

<session-type>Stateless</session-type>

<transaction-type>Container</transaction-type>

</session>

</enterprise-beans>

<assembly-descriptor>

<container-transaction>

<method>

<ejb-name>HelloWorld</ejb-name>

<method-name>*</method-name>

</method>

<trans-attribute>Required</trans-attribute>

</container-transaction>

</assembly-descriptor>

</ejb-jar>

程序清单2.10(ejb-insprise.xml)

<?xml version="1.0" encoding="GBK"?>

<!DOCTYPE inprise-specific PUBLIC '-//Inprise Corporation//DTD Enterprise

JavaBeans 1.1//EN''http://www.borland.com/devsupport/appserver/dtds/

ejb-inprise.dtd'>

<inprise-specific>

<enterprise-beans>

<session>

<ejb-name>HelloWorld</ejb-name>

<bean-home-name>HelloWorld</bean-home-name>

</session>

</enterprise-beans>

</inprise-specific>

________

第2 章 Enterprise JavaBeans

图2.10 JBuilder4 的EJB Deployment Descriptor 视图

图2.11 JBuilder4 的EJB Deployment Descriptor 图形化编辑界面

我们既可以在EJB DD Editor 面板中以图形化的方式编辑部署描述符也可以在EJB

DD Source 面板中直接以源代码级的方式编辑部署描述符文件编辑完以后千万不要忘

了对部署描述符进行验证(Verify)

________

第一部分 JSP 技术与J2EE 技术

2.5 如何开发EJB(二)

2.5.1 运行环境配置

本节我们接着介绍如何开发EJB 及如何配置EJB 组件的运行环境在JBuilder4 的主

窗口中左侧右键单击HelloWorldHome.java 选择Properties…. 选择Build Tab

VisiBroker 在Generate IIOP 前面的小方框前打勾如图2.12 所示

图2.12 配置Java2IIOP 的属性

单击OK 然后选择Run Configuration….. 如图2.13 所示

图2.13 配置EJB 的运行环境

在图2.13 中选中<Default> 单击OK 即可这样EJB 的运行环境就算配置好了

________

第2 章 Enterprise JavaBeans

2.5.2 创建EJB Container

本小节中我们将创建一个EJB Container 方法很简单只需要Make Project 就可以

了JBuilder4 会根据Remote Interface 和Home Interface 自动产生十几个Java 类文件这些

文件的名字如下所示

HelloWorldRemoteHelper.java

HelloWorldRemoteHolder.java

HelloWorldHomePOA.java

_HelloWorldHome_Stub.java

HelloWorldRemotePOAInvokeHandler.java

HelloWorldRemoteOperations.java

HelloWorldRemotePOA.java

_HelloWorldRemote_Stub.java

HelloWorldHomeHelper.java

HelloWorldHomeHolder.java

HelloWorldHomePOAInvokeHandler.java

HelloWorldHomeOperations.java

这些文件的作用就是产生Home Object 和EJB Object 须知EJB Container 就是由Home

Object 构成的在上面列举的文件中以_HelloWorldHome_Stub.java 和

_HelloWorldRemote_Stub.java 最为重要前者实现了Home Interface 所定义的create()方法

后者实现了Remote Interface 所定义的商业方法在本例中是getMessage()方法这两个文

件的源代码如程序清单2.11

程序清单2.11(_HelloWorldHome_Stub.java)

package helloworld;

public class _HelloWorldHome_Stub extends javax.ejb._EJBHome_Stub

implements HelloWorldHome HelloWorldHomeOperations

{

final public static java.lang.Class _opsClass =

helloworld.HelloWorldHomeOperations.class;

public java.lang.String[] _ids ()

{

return __ids;

}

private static java.lang.String[] __ids =

{

"RMI:helloworld.HelloWorldHome:0000000000000000"

"RMI:javax.ejb.EJBHome:0000000000000000"

};

________

第一部分 JSP 技术与J2EE 技术

public helloworld.HelloWorldRemote create ()

throws java.rmi.RemoteException javax.ejb.CreateException

{

try

{

while (true)

{

if (!_is_local())

{

org.omg.CORBA.portable.OutputStream _output = null;

org.omg.CORBA.portable.InputStream _input = null;

helloworld.HelloWorldRemote _result;

try

{

_output = this._request("create" true);

_input = this._invoke(_output);

//FIX: Cannot use helper class

//because of potential stub downloading

_result= (helloworld.HelloWorldRemote)

javax.rmi.PortableRemoteObject.narrow

(_input.read_Object

(helloworld.HelloWorldRemote.class)

helloworld.HelloWorldRemote.class);

return _result;

}

catch (org.omg.CORBA.portable.ApplicationException

_exception)

{

final org.omg.CORBA.portable.InputStream in =

_exception.getInputStream();

java.lang.String _exception_id =

_exception.getId();

if (_exception_id.equals("IDL:javax/ejb/CreateEx:1.0"))

{

_exception_id = in.read_string();

throw (javax.ejb.CreateException)

((org.omg.CORBA_2_3.portable.InputStream)in).

read_value();

}

//FIX: Wrap original Exception here?

throw new java.rmi.UnexpectedException

("Unexpected User Exception: " + _exception_id);

________

第2 章 Enterprise JavaBeans

}

catch (org.omg.CORBA.portable.RemarshalException

_exception)

{

continue;

}

finally

{

this._releaseReply(_input);

}

}

else

{

final org.omg.CORBA.portable.ServantObject _so =

_servant_preinvoke("create" _opsClass);

if (_so == null)

{

continue;

}

final helloworld.HelloWorldHomeOperations _self =

(helloworld.HelloWorldHomeOperations)_so.servant;

try

{

return _self.create();

}

finally

{

_servant_postinvoke(_so);

}

}

}

}

catch (org.omg.CORBA.portable.UnknownException ex)

{

if (ex.originalEx instanceof java.lang.RuntimeException)

{

throw (java.lang.RuntimeException) ex.originalEx;

}

else if (ex.originalEx instanceof Exception)

{

throw new java.rmi.ServerException(ex.getMessage()

(java.lang.Exception)ex.originalEx);

}

________

第一部分 JSP 技术与J2EE 技术

else

{

throw new java.rmi.ServerError

(ex.getMessage() (java.lang.Error) ex.originalEx);

}

}

catch (org.omg.CORBA.SystemException ex)

{

throw javax.rmi.CORBA.Util.mapSystemException(ex);

}

}

}

在程序清单2.11 中实现了create() 方法该方法是在Home

Interface(HelloWorldHome.java)中声明的该方法的返回值为HelloWorldRemote 对象

程序清单2.12(_HelloWorldRemote_Stub.java)

package helloworld;

public class _HelloWorldRemote_Stub extends javax.ejb._EJBObject_Stub

implements HelloWorldRemote HelloWorldRemoteOperations

{

final public static java.lang.Class _opsClass =

helloworld.HelloWorldRemoteOperations.class;

public java.lang.String[] _ids ()

{

return __ids;

}

private static java.lang.String[] __ids =

{

"RMI:helloworld.HelloWorldRemote:0000000000000000"

"RMI:javax.ejb.EJBObject:0000000000000000"

};

public java.lang.String getMessage () throws java.rmi.RemoteException

{

try

{

while (true)

{

if (!_is_local())

{

org.omg.CORBA.portable.OutputStream _output = null;

org.omg.CORBA.portable.InputStream _input = null;

java.lang.String _result;

________

第2 章 Enterprise JavaBeans

try

{

_output = this._request("_get_message" true);

_input = this._invoke(_output);

_result = org.omg.CORBA.WStringValueHelper.read

(_input);

return _result;

}

catch (org.omg.CORBA.portable.ApplicationException

_exception)

{

final org.omg.CORBA.portable.InputStream in =

_exception.getInputStream();

java.lang.String _exception_id =

_exception.getId();

//FIX: Wrap original Exception here?

throw new java.rmi.UnexpectedException("Unexpected

User Exception: " + _exception_id);

}

catch (org.omg.CORBA.portable.RemarshalException

_exception)

{

continue;

}

finally

{

this._releaseReply(_input);

}

}

else

{

final org.omg.CORBA.portable.ServantObject _so =

_servant_preinvoke("_get_message" _opsClass);

if (_so == null)

{

continue;

}

final helloworld.HelloWorldRemoteOperations

_self = (helloworld.HelloWorldRemo

teOperations)_so.servant;

try

{

return _self.getMessage();

}

finally

{

________

第一部分 JSP 技术与J2EE 技术

_servant_postinvoke(_so);

}

}

}

}

catch (org.omg.CORBA.portable.UnknownException ex)

{

if (ex.originalEx instanceof java.lang.RuntimeException)

{

throw (java.lang.RuntimeException) ex.originalEx;

}

else if (ex.originalEx instanceof Exception)

{

throw new java.rmi.ServerException(ex.getMessage()

(java.lang.Exception)ex.originalEx);

}

else

{

throw new java.rmi.ServerError(ex.getMessage()

(java.lang.Error) ex.originalEx);

}

}

catch (org.omg.CORBA.SystemException ex)

{

throw javax.rmi.CORBA.Util.mapSystemException(ex);

}

}

}

在程序清单2.15(_HelloWorldRemote_Stub.java)中实现了商业方法getMessage()

方法该方法是在Remote Interface(HelloWorldRemote.java)中声明的至于实现的细节我

们就不在这里讨论了

2.5.3 发布EJB 服务

经过上面这么多步的工作EJB 组件的开发应该告一段落了下面我们应该发布EJB

服务因为我们还不知道EJB 能不能够按照我们的设计正确运行所以暂时不把EJB 发布

到真正的EJB Server 上去而是使用IAS 服务器所带的Smart Agent 作为临时的EJB

Server/Container 发布我们刚刚编写好

JavaBeans 组件技术

第1 章 JavaBeans 组件技术

读者可以参考本书附录4 的内容

1.4.2 JavaBeans 和购物车功能

本小节中我们将要使用JavaBeans 组件实现一个简单的购物车请看程序清单

1.22(carts.html) 这是购物车的页面程序

程序清单1.22

<!—

File Name:carts.html

Author:fancy

Date:2001.4.1

Note:show the goods

-->

<html>

<head>

<title>carts</title>

</head>

<body bgcolor="white">

<font size = 5 color="#CC0000">

<form type=POST action=carts.jsp>

<BR>

Please enter item to add or remove:

<br>

Add Item:

<SELECT NAME="item">

<OPTION>Beavis & Butt-head Video collection

<OPTION>X-files movie

<OPTION>Twin peaks tapes

<OPTION>NIN CD

<OPTION>JSP Book

<OPTION>Concert tickets

<OPTION>Love life

<OPTION>Switch blade

<OPTION>Rex Rugs & Rock n' Roll

</SELECT>

<br> <br>

<INPUT TYPE=submit name="submit" value="add">

<INPUT TYPE=submit name="submit" value="remove">

</form>

</FONT>

</body>

________

第一部分 JSP 技术与J2EE 技术

</html>

程序清单1.22(carts.html)的作用是给用户提供一个购物的界面让他们可以自由地往

购物车中添加商品或者是删除某种商品程序清单1.22(carts.html)的运行效果如图1.11

所示

图1.11 carts.html 的运行效果

接下来请看程序清单1.23(DummyCart.java)

程序清单1.23

//File Name:DummyCart.java

//Author:fancy

//Date:2001.4.1

//Note:a cart

package test;

import javax.servlet.http.*;

import java.util.Vector;

import java.util.Enumeration;

public class DummyCart

{

Vector v = new Vector();

String submit = null;

String item = null;

private void addItem(String name)

{

v.addElement(name);

}

private void removeItem(String name)

{

________

第1 章 JavaBeans 组件技术

v.removeElement(name);

}

public void setItem(String name)

{

item = name;

}

public void setSubmit(String s)

{

submit = s;

}

public String[] getItems()

{

String[] s = new String[v.size()];

v.copyInto(s);

return s;

}

public void processRequest(HttpservletRequest request)

{

// null value for submit - user hit enter instead of clicking on

// "add" or "remove"

if (submit == null)

addItem(item);

if (submit.equals("add"))

addItem(item);

else if (submit.equals("remove"))

removeItem(item);

// reset at the end of the request

reset();

}

// reset

private void reset()

{

submit = null;

item = null;

}

}

________

第一部分 JSP 技术与J2EE 技术

在DummyCart.java 程序中定义了购物车的基本模型DummyCart 类使用Vector 数

据结构来模拟购物车的功能DummyCart 有三个属性分别是submit item v 其中submit

的值如果为add 那么意味着往购物车中添加商品如果为remove 那么表示用户将从购

物车中删除商品Item 代表用户需要添加或者舍弃的商品的名字v 是一个Vector 类型的

数据它保存着购物车的所有信息利用Vector 类的方法可以实现往购物车中添加商品

或者删除商品的操作在DummyCart 类中最重要的方法是processRequest() 这个方法判

断submit 的值然后调用addItem()方法或者removeItem()方法完成基本的购物车操作

下面请看程序清单1.24 carts.jsp

程序清单1.24

<%—

File Name:carts.jsp

Author:fancy

Date:2001.4.1

Note:to show cart

--%>

<html>

<jsp:useBean id="cart" scope="session" class="test.DummyCart" />

<jsp:setProperty name="cart" property="*" />

<%

cart.processRequest(request);

%>

<FONT size = 5 COLOR="#CC0000">

<br> You have the following items in your cart:

<ol>

<%

String[] items = cart.getItems();

for (int i=0; i<items.length; i++) {

%>

<li> <%= items[i] %>

<%

}

%>

</ol>

</FONT>

<hr>

<%@ include file ="carts.html" %>

</html>

用户首先打开carts.html 页面选中某种商品然后单击submit 按钮把数据提交到

carts.jsp 程序中carts.jsp 程序首先使用<jsp:useBean>创建一个新的Session Scope 类型的

JavaBean 组件对象cart 如果此cart 对象已经存在了那就不用创建了直接拿过来用就

是了Carts.jsp 程序接着使用<jsp:setProperty>操作指令给carts 对象赋值如下面的代码所

________

第1 章 JavaBeans 组件技术

<jsp:setProperty name="cart" property="*" />

读者一定注意到了上面的代码行中property 属性的值为* 这是什么意思呢?这就意味

着从客户端传递过来的任何参数只要名字和cart 对象的属性名字相符它们的值就会被

赋给cart 对象的相应的属性例如如果用户选择了JSP Book 这种商品单击了submit 按钮

那么JSP 引擎会自动调用cart 对象的addItem()方法和setSubmit()方法把item 属性的值设

为JSP Book 把submit 的值设为add

接下来carts.jsp 程序调用processRequest()方法让它根据submit 属性的值完成相应

的购物车操作最后carts.jsp 程序使用cart 对象的getItems()方法配合for 循环结构把

购物车的内容全部输出来

程序清单1.24 的运行效果如图1.12 所示

图1.12 carts.jsp 程序的运行效果

1.5 本 章 小 结

在本章中我们主要介绍了JavaBeans 组件技术在JSP 程序开发中的应用主要内容

包括

JavaBeans 的属性JavaBeans 的事件模型JSP 中与JavaBeans 相关的操作指令的语法

与用法JavaBeans 的开发流程JavaBeans 的Scope 属性JavaBeans 封装数据库操作等

内容这部分知识十分重要如果读者能够切实掌握JavaBean 组件技术并能够恰当地在

JSP 程序中使用它来封装一些复杂关键的操作那么你就会发现JSP 程序原来可以这样简

单但是功能又是这样强大

在下一章我们将向读者介绍目前如日中天的EJB 技术

________

第2 章 Enterprise JavaBeans

EJB(Enterprise JavaBeans)是一个全新的面向服务端的组件框架用于开发和部署面向

对象的分布式的企业级的Java 应用用EJB 技术编写的应用系统可扩展性好支持并发性

支持事务处理及多用户多线程环境下的安全性EJB 应用程序编写一次就可以在任何

支持EJB 技术的服务器平台上运行它提供了比以往的中间层解决方案更好的集成性与互

操作性因此对于高性能高可扩展性高安全性的应用系统通过使用EJB 组件技术

可以为用户提供更加高效的服务在本章我们就来介绍EJB 技术本章需要重点掌握的

内容如下所示

EJB 的体系结构

EJB 开发环境的配置

如何使用JBuilder 4.0+IAS 4.1 开发EJB

发布EJB 组件服务

编写EJB 组件服务的客户端

2.1 EJB 技术简介

2.1.1 EJB 技术的产生

EJB Enterprise JavaBeans 是Sun 公司推出的J2EE 产品家族的成员与其他J2EE

技术一起大大增强了Java 的能力并推动了Java 语言在企业级应用系统中的应用

目前企业间的电子商务应用系统开发以及分布式计算系统分布式处理系统的开发

成了时尚企业级应用的编程技术亦在不断发展企业级应用模式更在不断变化归纳起

来主要有以下几种发展趋势

从传统的Client/Server(客户端/服务端)模式向三层多层二维分布式应用模式转

对象组件技术的不断发展

命名(Naming)和目录(Directory)服务功能增强

大量采用中间件技术

根据企业级应用的发展趋势Sun 公司提出了一个名为JPE 的解决方案(全名为Java

Platform for the Enterprise 译为面向企业的Java 平台) 为企业级电子商务应用系统的发展

提供了坚实的基础在JPE 解决方案中Enterprise JavaBeans(EJB)技术是其核心技术之一

主要用于简化三层或者多层应用系统的开发进行大量的事务处理和分布式计算

EJB 技术与其他的J2EE 技术相互结合如JMS JTS JNDI RMI 等技术为企业级

电子商务系统的开发提供了一个安全可靠灵活以及伸缩性很强的开发平台EJB

Specification 1.0(EJB 规范1.0 版本)刚一提出就得到了包括IBM Weblogic Informix

Oracle Allaire Orion Netscape 等众多厂商的支持可以说EJB 将是企业级应用系统开

________

第2 章 Enterprise JavaBeans

发的一条未来之路

2.1.2 EJB 组件模型概括

EJB 并不是一个产品它是Java 服务器端服务框架的规范之一软件厂商根据这个规范

来实现EJB 容器(EJB 运行平台)的功能以便为客户端的应用程序提供EJB 组件服务应用

程序开发者可以专注于开发应用系统所需的商业逻辑而不用担心具体服务框架的实现问题

Sun 公司发布的EJB 规范详细地解释了一些最小但是必需的服务如事务处理安全

性命名和目录服务等等软件厂商必须遵循这些规范要求开发支持EJB 的产品以便

使得任何一个EJB 组件能够在任意支持EJB 的平台上使用某个EJB 规范所规定的必须实现

的服务EJB 规范并没有规定软件厂商如何实现这些服务它允许软件厂商在不牺牲EJB

核心服务的可移植性(与平台无关性)的前提下来提供一些增强功能

EJB 组件模型为中间层应用程序的开发提供了一致的基于组件的开发模型EJB 组件

模型像Java 技术规范所规定的那样write once run anywhere (一次编写处处运行)

具有与平台无关的平台独立性是一种开放的组件模型而目前除了EJB 以外的其他中间

层解决方案一般都是基于专有的应用平台同时需要有大量的底层代码开发工作跨平

台应用(移植)有困难EJB 组件模型对这些方案进行了标准化提供了一个标准的与平台无

关的组件运行环境EJB Server 用户不需要自己去编程EJB Server 可以负责处理远过

程调用事务处理安全性操作多线程应用和组件状态管理等底层操作简化了应用系

统的开发过程以前这些处理都需要系统开发者编程实现开发的难度很大

EJB 组件模型增强了面向客户端的JavaBeans 组件模型的功能适用于整个企业分布

式大规模应用系统的开发我们在第一章已经介绍了一些关于JavaBeans 组件技术的内容

那么EJB 组件模型与JavaBeans 组件模型相比又有何不同呢?

JavaBeans 组件模型特点

JavaBeans 组件模型是面向客户端的组件模型它支持可移植和可重用的Java 组件的

开发JavaBeans 组件可以工作于任何Java 程序应用开发工具中Java 程序开发者可以利

用Java 集成开发环境开发Java Application Java Applet 及Java Servlet 等程序我们可以认

为JavaBeans 组件是一个特殊的Java Class 可以被加入到应用开发工程中并被Java 程

序开发者所使用在Java 集成开发环境中Java 程序开发者可以通过改变JavaBeans 组件

的属性表或通过特定的setXXX()方法getXXX()方法来定制JavaBean 组件的外观和运行状

态多个JavaBeans 组件可以组合在一起构成Java Applet 或Java Application JavaBeans

JavaBeans 组件总是在程序运行时被实例化它支持可视化及非可视化的组件模型

EJB 组件模型特点

EJB是面向服务端的JavaBeans 组件模型它是一种特殊的非可视化的JavaBeans

运行在服务器上EJB 组件同普通的JavaBeans 组件一样可以和其他JavaBeans

组件一起建立新的Java 应用程序EJB 对象也可以通过改变它的属性表或者使用

它的定制方法来进行处理和定制

EJB 组件模型主要包括EJB Server(EJB 服务器) EJB Container(EJB 容器) EJB

________

第一部分 JSP 技术与J2EE 技术

Object(EJB 对象)以及诸多相关特性

EJB Server(EJB 服务器)提供EJB 组件的运行环境它负责管理和协调应用程序资

源的分配CORBA 运行系统Web Server(如WebLogic 服务器) 数据库系统(例

如Oracle9i 数据库服务器)都可以作为EJB Server EJB Server 必须提供EJB

Container(EJB 容器)

EJB Container(EJB 容器)是用于管理EJB Object(EJB 对象)的设备它负责EJB 对

象的生命周期的管理实现EJB 对象的安全性协调分布式事务处理并负责EJB

对象的上下文切换EJB Container 还可以管理EJB 对象的状态某些情况下EJB

对象数据是短暂的(例如会话EJB 对象) 只存在于特定的方法调用过程中另一些

情况下EJB 对象数据是长久的(例如实体EJB 对象) 多个访问都要调用此EJB

对象数据EJB 容器必须同时支持短暂对象数据及长久对象数据EJB 对象被赋予

EJB Container 当EJB 对象被使用时你可以通过修改其环境属性来定制EJB 对

象的运行状态特性比如开发者可以使用EJB Container 用户接口提供的工具来

修改EJB 对象的事务模式及安全属性EJB 对象一经使用EJB Container 负责管

理EJB 对象生命周期EJB 对象安全特性和协调事务处理

注意在这里EJB Object(EJB 对象)的定义是不严格的指的是EJB 组件中实现了商

业逻辑的部分严格的EJB Object 的定义请参考2.3.1 小节在2.3.1 小节以前

我们在文中出现EJB Object 或者EJB 对象的地方意义均如上所述EJB 远程

对象与EJB 对象的意义是不同的EJB 远程接口对象指的是EJB Remote 接口

的实例对象

EJB 规范提供了这样的一种机制你可以通过在运行时设置相应的属性值来定义

每一个EJB 对象的运行状态其实这种功能和JavaBeans 组件对象的相应功能

是十分类似的每一个EJB 对象都需要提供Deployment Descriptor 包括Entity

Descriptor 或Session Descriptor

Deployment Descriptor 被用于设置EJB 对象的运行状态这些设置值告诉EJB

Container 如何去管理和控制EJB 对象它们可以在应用程序组装(开发)或应用程

序运行时进行设置典型地针对EJB 对象的设置属性包括生命周期持久性

事务属性和安全特性

JavaBeans 组件模型与EJB 组件模型的比较

在前面我们已经分别提到过JavaBeans 和EJB 的异同但是在这里我们仍然要再次比

较JavaBeans 和EJB 组件模型的一些特征JavaBeans 是Java 的组件模型在JavaBeans 规

范中定义了事件监听事件引发和JavaBeans 属性等特征EJB 也定义了一个Java 组件模

型但是EJB 组件模型和JavaBeans 组件模型是不同的两个模型首先这两个模型的侧

重点不同JavaBeans 组件模型的重点在于允许开发者在Java 集成开发环境中可视化地操

纵JavaBeans 组件把若干个JavaBeans 组件拼装成为一个完整的Java 应用程序JavaBeans

规范详细地解释了组件间事件监听者的注册注销事件的引发事件的传递事件的过

滤识别和属性使用JavaBeans 的定制和持久化的应用编程接口等诸多内容EJB 组件模型

________

第2 章 Enterprise JavaBeans

的侧重点则在于定义了一个可以便携地部署Java 组件的服务框架模型因此其中并没提

及事件的传递和处理因为EJB 组件通常不发送事件和监听事件的发生同样也没有提及

属性的定制EJB 对象的属性定制并不是在开发时进行而是在EJB 组件运行时刻(实际

上在部署组件时通过一个部署描述符(Deployment Descriptor)来定制的定制的EJB 对象

的属性一般是生命周期持久性事务属性和安全特性等属性

不要试图寻找JavaBeans 和EJB 组件模型之间的相似性它们都是Java 组件模型规范

但是前者说明了在集成开发环境中应用程序如何组装比较高效的问题而后者则侧重于部

署EJB 组件的服务平台的细节不要错误地认为JavaBeans 组件是用于客户端程序的开发

(实际上JavaBeans 可以用于客户端程序的开发例如Java Applet 小程序中可以使用

JavaBeans 不过这不是JavaBeans 组件的主要用途) JavaBeans 组件主要用于服务端程序

的开发(如本书第一章所述) 同样EJB 组件也是用于服务器端的程序开发JavaBeans 可

作为进行非图形化的服务器端的Java 应用开发的组件使用区别是当你使用JavaBeans 创

建服务器端应用程序时你还得设计整个的服务框架使用EJB 组件那么服务框架是现

成的由EJB 平台自动支持的你只需要使用EJB 的API 即可对于复杂的服务器端应用

程序显然使用EJB 比使用JavaBeans 更简单

会话EJB 和实体EJB

EJB 技术规范规定有两种类型的EJB 对象它们分别是Session EJB(会话EJB)和Entity

EJB(实体EJB)

Session EJB(会话EJB)是短暂存在的对象它同样运行在服务器端并执行一些应用

逻辑处理它的实例对象由客户端应用程序建立并仅能够被该应用程序所使用其保存

的数据需要开发者编写程序来管理我们通常使用Session EJB 来完成数据库访问或数据计

算等工作Session EJB 支持事务属性但是当系统因为某种特殊的不可预见的原因崩溃

或者关闭后Session EJB 的状态数据不会再被系统恢复Session EJB 的这种特性十分类

似于Page Scope 类型的JavaBean 组件对象

Entity EJB(实体EJB)是持久运行的EJB 对象由某个客户端应用程序创建但是可以

被其他对象或者其他的应用程序调用与Session EJB 不同Entity EJB 必须在建立时制定

一个唯一的标识并提供相应的机制允许客户应用程序根据Entity EJB 标识来定位Entity

EJB 的实例对象多个用户可以并发访问Entity EJB 事务之间的协调工作由EJB Container

或者EJB 类自身来完成读者应当注意Session EJB 只能够被创建它的应用程序所调用

所以不存在事务协调的问题Entity EJB 支持事务处理当系统停机时也可以恢复停机以

前的状态包括EJB 对象所数据在内EJB 规范中定义了两种处理Entity EJB 的持久性模

型即Beans Managed Peresistent(Bean 自管理模式亦即BMP 模式)及Container Managed

Peresistent(容器管理模式亦即CMP 模式) BMP 模式是由EJB 对象自己来管理持久性

它需要EJB 开发者来编写数据库或应用程序的处理逻辑并加入到EJB 对象类(EJBObject

Class)的ejbCreate() ejbRemove() ejbFind() ejbLoad()和ejbStore()等方法中CMP 模式

是将EJB 持久性管理交给EJB Container 来完成开发者一般要在EJB 对象的Deployment

Descriptor 中的Container Managed 字段属性中指定EJB 实例对象的持久性作用域当使用

CMP 模式时不需要用户知道Entity EJB 所存储的数据源也不需要用户参与复杂烦琐

________

第一部分 JSP 技术与J2EE 技术

的编码工作

EJB Container 的作用

EJB Container 在EJB 环境下主要起到如下作用

EJB Container 负责提供协调管理事务处理和RMI(远程方法调用)等功能

EJB Container 负责建立EJB 对象的运行上下文环境(Context) 负责切换协调不同

类型的EJB 对象

EJB Container 可以有不同类型如DBMS Web Server 等如果某个厂商宣布支持EJB

模型那么一般是提供不同应用的EJB Container 例如Oracle 公司支持EJB 是通过Oracle

数据库服务器BEA 公司支持EJB 是通过它的WebLogic Web 服务器

客户端应用程序通常不和EJB 直接打交道它们要通过EJB Container 提供的Home

Object(Home 接口的实现)访问EJB 对象 该接口提供了EJB 对象的客户方视角使用EJB

Container 提供的Home 接口允许客户建立或删除EJB 远程对象对Entity EJB 对象来说

Home 接口还提供定位特定的Entity EJB 实例对象的功能

当用户请求EJB 服务时它首先要通过JNDI 技术来定位对象的Home 接口EJB 对

象类(EJB Object Class)及其远程接口(Remote Interface)对Home 接口来说是透明的Home

接口必须提供建立EJB 远程接口对象的方法一旦客户端程序找到并定位所需的Home 接

口以后它就可以通过调用Home 接口中的生成方法(create())建立EJB 远程接口对象

EJB 对象(EJB Object)是EJB Container 提供的EJB 类的一个运行实例它用来实现EJB

的远程调用接口(上文已经提到过EJB 对象有两类分别是Session EJB 和Entity EJB)

用户总是通过调用EJB 远程接口方法调用来间接调用EJB 对象的服务通常是使用RMI

技术用户调用EJB 对象的服务时由EJB Container 接受请求并将任务交给EJB 对象

这种机制保证为用户及EJB 提供透明的状态管理事务控制及安全性服务

Home 接口

Home 接口定义了创建查找删除EJB 远程接口对象或者EJB 服务的方法客户端

应用程序通过使用JNDI 技术定位Home 接口的位置一旦获得Home 接口的实例就可以

利用它的create()方法创建EJB 远程接口的实例对象进而调用EJB 对象的方法实现特定

的功能

Remote 接口

远程调用接口 (或者简称为远程接口Remote Interface)是对EJB 对象方法的总封装

在远程调用接口中声明了EJB 对象的方法但是并没有具体实现这些方法EJB 对象具

体实现了这些方法当我们利用Hmoe 接口实例对象的方法创建EJB 对象时实际上创建

的却是EJB 远程调用接口的实例当我们调用这些实例的方法时由于远程接口没有实现

这些方法所以调用的却是EJB 对象相应的方法利用远程接口的好处在于这样做可以

把某些EJB 对象的方法屏蔽起来不让用户访问

每一个EJB 都必须有一个Remote 接口Remote 接口定义了应用程序规定客户可以调

用的逻辑操作这是一些可以由客户端程序调用的公共的方法通常由EJB 对象类来实现

________

第2 章 Enterprise JavaBeans

注意EJB 的客户并不直接访问EJB 而是通过Remote 接口来访问的

2.1.3 EJB 技术的未来

Sun 公司公布的EJB1.0 规范是EJB 规范的第一个版本随着J2EE 技术的发展以及建

立企业电子商务应用系统的迫切需求EJB 技术还会进一步的完善与发展EJB1.0 只规定

必须支持Session EJB EJB 2.0 规范中Entity EJB 也必须支持目前很多厂商都宣布支

持EJB1.0 /EJB 1.1 规范并同时提供了对Session EJB/Entity EJB 的支持如IBM 的San

Francisco Project BEA 公司更是一马当先推出的WebLogic 6.0 Web 服务器系统是世界上

第一个宣布支持EJB 2.0 规范的运行平台其实这就是一个EJB Server/Container 目前

进程管理线程池并发控制和资源管理等功能未包含在EJB 1.0 规范中因此对这些

服务的支持程度也就决定了不同厂商的EJB 产品的差别与不同大多数厂商都是将自己

原来的中间层解决方案移植到EJB Container 中.EJB 技术的发展方向是要支持多种中间层

环境包括

CORBA 平台比较典型的如Borland 公司的VisiBroker for Java

DBMS(数据库管理系统) 如Informix Oracle Sybase 等数据库服务器

Web 服务器如BEA WebLogic Netscape iPlanet Web Server Oracle Application Server

等关于市场上流行的支持EJB 组件模型的开发工具和服务器的列表请参考本书附录一

从企业应用多层结构的角度EJB 是商业逻辑层的构件技术与JavaBean 不同他提

供了事务处理的能力自从三层结构提出中间层也就是商业逻辑层是处理事务的核

心由于从数据存储层分离它就取代了存储进程的大部分地位从分布式计算的角度

EJB 像CORBA 一样提供了分布式技术的基础以及对象之间的通讯手段从Internet

技术应用的角度EJB 和Servlet JSP 一起成为新一代应用服务器的技术标准EJB 中的

Bean 可以分为会话Bean 和实体Bean 前者维护会话后者处理事务现在Servlet 程序

负责与客户端通信访问EJB 并把结果通过JSP 产生页面传回客户端成为开发的新潮

流从发展的角度看EJB 完全有可能成为面向对象数据库的新平台构成企业计算的基

总而言之在日新月异的技术发展和更新中EJB 甚至EJB 技术的后继者将在J2EE

技术的大旗下不断攻城略地占领企业计算的大好江山在下一节我们将详细介绍EJB

组件模型的结构

2.2 EJB 体系结构(一)

2.2.1 EJB 组件如何工作

通过上面的介绍我们对EJB 的组件模型应该有了初步的了解但这是十分肤浅的

如果想凭这一点知识去开发EJB 应用系统那无疑是天方夜谭下面我们首先简要地介绍

一下EJB 组件模型的运行原理然后再结合运行原理详细介绍EJB 组件模型请看图2.1

________

图2.1 EJB 应用系统运行原理

图2.1 是一个最最简单的EJB 应用系统的运行原理图不会吧这还是最简单的系统?

是的这确实是最简单的EJB 应用系统实际上的EJB 应用系统要比它还要复杂很多倍

不过读者也不用害怕EJB 系统刚开始学的时候觉得很复杂很难毫无头绪但是随着

学习的深入与开发经验的增加你就会觉得EJB 技术原来也是那么简单

闲话少说现在我们转入正题图2.1 明显地可以分为三个层次从左到右看第一

层包括Client 和Web Server 其实这一层可以再分为两层分别是Client 层与Server 层

但是为了简化系统结构起见我们还是把它们归为一层Client 端向Server 端发送请求

Server 端响应Client 端的请求这就是普通的C/S 模式Server 可以直接反馈信息到客户

端而不经过任何其他的中间件但是Server 端也可以把客户端的请求发送到某个特定的

应用系统中由应用系统对这个请求进行处理然后再把结果返回Server 端Server 端再

把结果返回Client 端这样做的好处是Server 端可以专注于响应客户端的请求而把繁重

的计算工作分发到其它的应用系统中进行处理Server 端只是起了一个信息流交换媒介的

作用这样的处理模式在大流量重负荷状态下运行具有明显的优越性实际上应用系

统还可以把客户端的请求按照其性质再次分发到下一级的应用系统中进行处理这样就可

以构造一个高效的树状计算阵列对输入信息流进行接受过滤分发处理反馈等操

作EJB 组件就是这种类型的应用系统

图2.1 中的第二层是所谓的应用系统层EJB 服务就主要驻留在这一层这一层可以

是任何可用的中间件解决方案这与系统平台有很大的关系但是本章的主题是EJB 技术

所以我们就假定第二层完全是由EJB 组件构成的下面的讨论也都是以这个假定为出发点

第二层由下面的部分组成EJB Server EJB Container Remote Interface Home Interface

EJB Object 等应用系统层是如何工作的呢?Web Server 把客户端的请求分发到应用系统层

首要的目标是找到提供特定服务的EJB 组件Web Server 透过EJB Server 层与EJB

Container 通信查找并且定位Home 对象Home 对象是EJB 对象与客户端应用程序(这里

的客户端是相对而言的因为第一层相对于第二层就是客户端了第二层的应用系统相对

________

第2 章 EnterpriseJavaBeans

于第三层也是客户端了)之间通信的接口当找到特定的Home 对象时我们就可以利用这

个对象创建一个Remote 对象这个Remote 对象封装了EJB 对象的所有功能在应用程

序中调用Remote 对象的方法实际上就是调用EJB 对象的方法在这个EJB 对象的方法

中还可以调用另一个EJB 对象的方法第二个EJB 对象可能存在另一个EJB Container(同

一个EJB Server)中甚至还有可能存在于另一个EJB Server 的EJB Container 中不过这两

个EJB 对象是否存在于同一个EJB Server 中并非问题的关键关键之处在于第一个EJB 对

象也成了第二个EJB 对象的客户端程序了第一个EJB 对象的还可以调用另外的应用系统

来完成特定的任务例如CORBA DBMS 系统这些另外的应用系统就构成了图2.1 中的

第三层(包含EJB Database Other Enterprise System)

我们特别需要注意的是在第一层运行的应用程序通过调用第二层的Home 对象的

create()方法创建Remote 对象并调用Remote 对象的方法从应用程序的代码上看似乎

Remote 对象是驻留在第一层的服务器的内存空间中但事实上却并非如此Remote 对象

驻留并运行于第二层EJB Server 的内存空间中它把执行的结果直接返回第一层的应用程

序不需要再通过Home 对象了第一层的应用程序应该分析执行的结果并把数据送到

客户端(这才是真正的客户端不提供任何服务只享受服务)的浏览器中

本章的任务就是向读者介绍如何使用EJB技术构造服务端的第二层应用系统层

下面我们就来仔细分析一下EJB 组件模型的具体细节

2.2.2 EJB Server

EJB 服务器(EJB Server)是管理EJB 容器(EJB Container)的高端进程或应用程序并提

供对系统服务的访问EJB 服务器也可以提供厂商自己的特性如优化的数据库访问接口

对其他服务如CORBA 服务的访问对SSL3.0 的支持等一个EJB 服务器必须提供对

JNDI 命名和目录服务和事务服务的支持一些可能的EJB 服务器的例子如

数据库服务器典型的例子如Oracle 8i/9i 数据库服务器

Web 服务器典型的例子如BEA 的WebLogic Web 服务器IBM公司的WebSphere

服务器

中间件服务器如Borland 公司的Smart Agent

JavaBeans组件的用法

JavaBeans组件的用法

程序清单1.17

//File Name:JspCalendar.java

//Author:fancy

//Date:2001.3.26

//Note:use this JavaBean to get current time

package test;

import java.text.DateFormat;

import java.util.*;

public class JspCalendar

{

Calendar calendar = null;

public JspCalendar()

{

calendar = Calendar.getInstance(TimeZone.getTimeZone("PST"));

Date trialTime = new Date();

calendar.setTime(trialTime);

________

第一部分 JSP技术与J2EE技术

}

public int getYear()

{

return calendar.get(Calendar.YEAR);

}

public String getMonth()

{

int m = getMonthInt();

String[] months = new String []

{"January" "February" "March"

"April" "May" "June"

"July" "August" "September"

"October" "November" "December"

};

if (m > 12)

return "Unknown to Man";

return months[m - 1];

}

public String getDay()

{

int x = getDayOfWeek();

String[] days = new String[]

{"Sunday" "Monday" "Tuesday" "Wednesday"

"Thursday" "Friday" "Saturday"

};

if (x > 7)

return "Unknown to Man";

return days[x - 1];

}

public int getMonthInt()

{

return 1 + calendar.get(Calendar.MONTH);

}

public String getDate()

________

第1 章 JavaBeans 组件技术

{

return getMonthInt() + "/" + getDayOfMonth() + "/" + getYear();

}

public String getTime()

{

return getHour() + ":" + getMinute() + ":" + getSecond();

}

public int getDayOfMonth()

{

return calendar.get(Calendar.DAY_OF_MONTH);

}

public int getDayOfYear()

{

return calendar.get(Calendar.DAY_OF_YEAR);

}

public int getWeekOfYear()

{

return calendar.get(Calendar.WEEK_OF_YEAR);

}

public int getWeekOfMonth()

{

return calendar.get(Calendar.WEEK_OF_MONTH);

}

public int getDayOfWeek()

{

return calendar.get(Calendar.DAY_OF_WEEK);

}

public int getHour()

{

return calendar.get(Calendar.HOUR_OF_DAY);

}

public int getMinute()

{

return calendar.get(Calendar.MINUTE);

________

第一部分 JSP 技术与J2EE 技术

}

public int getSecond()

{

return calendar.get(Calendar.SECOND);

}

public int getEra()

{

return calendar.get(Calendar.ERA);

}

public String getUSTimeZone()

{

String[] zones = new String[]

{"Hawaii" "Alaskan" "Pacific"

"Mountain" "Central" "Eastern"

};

int index = 10 + getZoneOffset();

if (index <= 5)

{

return zones[10 + getZoneOffset()];

}

else

{

return "Only US Time Zones supported";

}

}

public int getZoneOffset()

{

return calendar.get(Calendar.ZONE_OFFSET)/(60*60*1000);

}

public int getDSTOffset()

{

return calendar.get(Calendar.DST_OFFSET)/(60*60*1000);

}

public int getAMPM()

________

第1 章 JavaBeans 组件技术

{

return calendar.get(Calendar.AM_PM);

}

}

在程序清单1.17(JspCalendar.java)中定义了一个JavaBean JspCalendar JspCalendar

组件中有很多getXXX()方法可以获取各种各样的时间信息关于这些方法的细节问题

我们在这里就不详细讨论了这不是本书的主题对此感兴趣的读者可以参考相应的Java

文档

程序清单1.18

<%--

File Name:date.jsp

Author:fancy

Date:2001.4.1

Note:use JspCalendar bean to show the time info.

--%>

<html>

<body bgcolor="white">

<jsp:useBean id='clock' scope='page' />

<font size=4>

<ul>

<li>Day of month: is <jsp:getProperty name="clock" property="dayOfMonth"/>

<li>Year: is <jsp:getProperty name="clock" property="year"/>

<li>Month: is <jsp:getProperty name="clock" property="month"/>

<li>Time: is <jsp:getProperty name="clock" property="time"/>

<li>Date: is <jsp:getProperty name="clock" property="date"/>

<li>Day: is <jsp:getProperty name="clock" property="day"/>

<li>Day Of Year: is <jsp:getProperty name="clock" property="dayOfYear"/>

<li>Week Of Year: is <jsp:getProperty name="clock" property="weekOfYear"/>

<li>era: is <jsp:getProperty name="clock" property="era"/>

<li>DST Offset: is <jsp:getProperty name="clock" property="dSTOffset"/>

<li>Zone Offset: is <jsp:getProperty name="clock" property="zoneOffset"/>

</ul>

</font>

</body>

</html>

程序清单1.18(date.jsp)程序十分简单只是调用JspCalendar Bean 的各种getXXX()方

法输出各种各样的时间信息读者请注意JspCalendar Bean 的Scope 属性是page 这就

是说每次刷新当前页面时该JavaBean 对象就会被重新创建重新获取时间信息所以

每次执行date.jsp 程序每次的结果都不一样程序清单1.18 的执行结果见图1.7 图1.8

________

第一部分 JSP 技术与J2EE 技术

图1.7 date.jsp 程序的运行结果(第一次运行)

图1.8 date.jsp 程序的运行结果(第二次运行)

由图1.7 和图1.8 不难看出date.jsp 程序前后两次的运行结果显然不同

接下来请看下面的程序清单1.19(date1.jsp)

程序清单1.19

<%--

File Name:date.jsp

Author:fancy

Date:2001.4.1

Note:use JspCalendar bean to show the time info.

--%>

<html>

<body bgcolor="white">

<jsp:useBean id='clock' scope='application' />

________

第1 章 JavaBeans 组件技术

<font size=4>

<ul>

<li>Day of month: is <jsp:getProperty name="clock" property="dayOfMonth"/>

<li>Year: is <jsp:getProperty name="clock" property="year"/>

<li>Month: is <jsp:getProperty name="clock" property="month"/>

<li>Time: is <jsp:getProperty name="clock" property="time"/>

<li>Date: is <jsp:getProperty name="clock" property="date"/>

<li>Day: is <jsp:getProperty name="clock" property="day"/>

<li>Day Of Year: is <jsp:getProperty name="clock" property="dayOfYear"/>

<li>Week Of Year: is <jsp:getProperty name="clock" property="weekOfYear"/>

<li>era: is <jsp:getProperty name="clock" property="era"/>

<li>DST Offset: is <jsp:getProperty name="clock" property="dSTOffset"/>

<li>Zone Offset: is <jsp:getProperty name="clock" property="zoneOffset"/>

</ul>

</font>

实际时间:<%=new java.util.Date()%>

</body>

</html>

程序清单1.19 和程序清单1.18 几乎完全相同只不过在程序清单1.19 中JspCalendar

组件的Scope 属性值为application 而程序清单1.19 中JspCalendar 组件的Scope 属性值为

page 并且在程序清单1.19 的最后还使用了另一种方法获取当前的时间以便对照那么

程序清单1.19 的运行效果与程序清单1.18 相比究竟有什么不同呢?请看图1.8 和图1.9

图1.8 date1.jsp 程序的运行效果(第一次)

________

第一部分 JSP 技术与J2EE 技术

图1.9 date1.jsp 程序的运行效果(第二次)

由图1.8 不难看出两种方法获取的时间一样在图1.9 中第一种方法获取的时间

和第二种方法获取的时间有着明显的差距大概有1 分半钟左右即使考虑到代码执行先

后的时间间隔也没有办法解释有这样大的时间差距但是第一种方法获取的时间(图1.9)

和图1.8 所显示的时间一模一样这说明了一个问题即在date1.jsp 程序中JspCalander

组件的代码只被执行一次不管如何刷新页面JspCalendar Bean 组件的getXXX()方法都

是返回JspCalendar Bean 组件第一次被初始化时的时间这就是Application Scope 类型的

JavaBeans 与Page Scope 类型的JavaBeans 的不同之处

1.4 JavaBeans 应用实例

在这一节中我们将结合以前介绍过的知识讨论JavaBeans 组件技术如何与JSP 技

术结合在一起开发功能强大的JSP 程序限于篇幅我们只能够介绍如何用JavaBeans

封装数据库操作和购物车功能这两个方面的内容至于其他的方面读者可以仿照下面的

例子自己探索

1.4.1 JavaBeans 封装数据库操作

这一小节我们就来介绍如何使用JavaBeans 封装数据库操作的功能我们首先需要创

建一个可以访问数据库的JavaBean 组件然后在JSP 程序中调用它请看程序清单

1.20(JDBCBean.java)

程序清单1.20

//File Name:

//Author:fancy

//Date:2001.4.1

//Note:to visit the database

________

第1 章 JavaBeans 组件技术

package test;

import java.io.*;

import java.sql.*;

public class JDBCBean

{

String Hello="hello world I am fancy";

public void JDBCBean()

{

}

public ResultSet connect()

{

try

{

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

Connection conn = DriverManager.getConnection("jdbc:odbc:test" "sa" "");

Statement stmt=conn.createStatement();

ResultSet rs=stmt.executeQuery("USE fancy SELECT * FROM goods");

return rs;

}

catch(Exception fe)

{

}

return null;

}

}

JDBCBean.java 程序中定义了JDBCBean 类使用该类的connect()方法可以访问数据

库JDBCBean 类的connect()方法采用标准的流程访问数据库首先是载入数据库驱动程

序然后是创建数据库连接对象conn 利用这个连接对象创建SQL 语句对象stmt 接下来

利用stmt 对象的executeQuery()方法向数据库系统发送SQL 语句该方法的返回值是

ResultSet 接口的实例对象

在JSP 程序中如何使用这个JavaBean 组件访问数据库呢? 请看程序清单

1.21(JDBCBean.jsp)

程序清单1.21

<%--

File Name:JDBCBean.jsp

Author:fancy

________

第一部分 JSP 技术与J2EE 技术

Date:2001.3.26

Note:use JDBCBean to access the db System

--%>

<%@ page import="java.sql.*"%>

<jsp:useBean id="db" class="test.JDBCBean" />

<%

ResultSet rs=db.connect();

while(rs.next())

{

out.println(rs.getObject(1)+"-->"+rs.getObject(3)+"<br>");

}

%>

在程序清单1.21(JDBCBean.java)中应用JDBCBean Bean 实现了访问数据库的功能

首先使用<jsp:useBean>操作指令创建JDBCBean 组件的实例对象db 然后在JSP 程序段中

直接调用db 对象的connect()方法获取ResultSet 接口的实例对象rs 接下来使用一个while

循环结构遍历这个记录集对象获取记录集第三个字段的值并把它们一一输出

程序清单1.21 的运行效果如图1.10 所示

图1.10 JDBCBean.jsp 程序的运行效果

上面所举的例子十分简单例如SQL 语句已经指定了我们没有办法改变它除非重

新编写JDBCBean.java 程序那么如何解决这个问题呢?答案是重载JDBCBean 类的connect()

方法让它可以接受更多的数据库访问参数包括用户名密码SQL 语句等参数这样

对于JDBCBean.jsp 程序来说无需作多大改动甚至不需要改动功能也可以大大扩展

注意本例所使用的JDBC 驱动程序为JDBC-ODBC 桥数据库系统为MS SQL

Server7.0 Desktop Edition 操作系统为Windows Me 关于数据库的结构信息

________

JSP 技术与J2EE 技术

JSP 技术与J2EE 技术

第1 章 JavaBeans组件技术

本章将要向读者介绍JavaBeans 组件技术在JSP 程序开发中的应用在JSP 深入编程

中我们已经介绍了一点关于JavaBeans 的知识但是由于体系结构的原因我们并没有

深入讨论它也许有的读者对此还有些遗憾不过不要紧这一章就来弥补读者的这个遗

憾本章中读者需要重点掌握的内容有

JavaBeans的属性

JavaBeans的事件模型

JSP 中与JavaBeans 相关的操作指令的语法与用法

JavaBeans的开发流程

JavaBeans的Scope 属性

JavaBeans封装数据库操作

1.1 什么是JavaBeans

1.1.1 JavaBeans 简介

软件开发的真正目的之一是利用在程序编码方面的投资以便在同一公司或者不同公

司的其他开发中重用程序编码近年来编程人员投入大量精力以便建立可重用的软件

可重用的软件组件早期用在面向对象编程方面中的投资已经在Java c#编程语言的开

发中充分实现很多软件可以不用做很大的改变就可以运行在各种平台上

JavaBeans描述了Java 的软件组件模型这个模型被设计成使第三方厂家可以生成和

销售能够集成到其他开发厂家或者其他开发人员开发的软件产品的Java 组件

应用程序开发者可以从开发厂家购买现成的JavaBeans 组件拖放到集成开发环境的工

具箱中再将其应用于应用软件的开发对于JavaBeans 组件的属性行为可以进行必要的

修改测试和修订而不必重新编写和编译程序在JavaBeans 模型中JavaBeans 组件可以被

修改或者与其他JavaBeans 组件组合以生成新的JavaBeans 组件或完整的Java 应用程序

Java 应用程序在运行时最终用户也可以通过JavaBeans 组件设计者或应用程序开发

者所建立的属性存取方法setXXX 方法和getXXX 方法修改JavaBeans 组件的属性这

些属性可能是颜色和形状等简单属性也可能是影响JavaBeans组件总体行为的复杂属性

JavaBeans组件模型使得软件可以设计成便于修改和便于升级每个JavaBeans组件都

包含了一组属性操作和事件处理器将若干个JavaBeans 组件组合起来就可以生成设计

者开发者所需要的特定运行行为JavaBeans 组件存放于容器或工具库中供开发者开发

应用程序

JavaBeans就是一个可以复用软件模型JavaBeans在某个容器中运行提供具体的操

作性能JavaBeans 是建立应用程序的建筑模块大多数常用的JavaBeans 通常是中小型控

制程序但我们也可以编写包装整个应用程序运行逻辑的JavaBeans组件 并将其嵌入到

复合文档中以便实现更为复杂的功能

一般来说JavaBeans 可以表示为简单的GUI 组件可以是按钮组件游标菜单等

等这些简单的JavaBeans 组件提供了告诉用户什么是JavaBeans 的直观方法但我们也可

以编写一些不可见的JavaBeans 用于接受事件和在幕后工作例如访问数据库执行查询

操作的JavaBeans 它们在运行时刻不需要任何可视的界面在JSP 程序中所用的JavaBeans

一般以不可见的组件为主可见的JavaBeans 一般用于编写Applet 程序或者Java 应用程序

1.1.2 JavaBeans 属性

JavaBeans的属性与一般Java 程序中所指的属性或者说与所有面向对象的程序设计

语言中对象的属性是同一个概念在程序中的具体体现就是类中的变量在JavaBeans的

设计中按照属性的不同作用又细分为4 类Simple 属性Index 属性Bound 属性与

Constrained属性

Simple 属性

一个Simple 类型的属性表示一个伴随有一对getXXX() setXXX()方法的变量属性

的名称与和该属性相关的getXXX() setXXX()方法相对应例如如果有setX()和getX()

方法则暗指有一个名为"X"的属性如果有一个方法名为isX() 则通常暗指"X"是一个布

尔类型的属性请看下面的程序清单1.1(JavaBean1.java)

程序清单1.1

//File Name:JavaBean1.java

//Author:fancy

//Date:2001.3.29

//Note:create a simple javabean

public class JavaBean1

{

String ourString= "Hello";

public JavaBean1()

{

}

public void setoutString(String newString)

________

第1 章JavaBeans 组件技术

{

ourString=newString;

}

public String getoutString()

{

return ourString;

}

}

在程序清单1.1(JavaBean1.java)中我们定义了一个JavaBean JavaBean1 其实也

就是定义了一个JavaBean1 类JavaBean1 有一个名为outString 的字符串类型的属性与这

个属性相对应的方法为setoutString()和getoutString() 使用这两个方法可以存取outString

属性的值

Indexed 属性

一个Indexed 类型的JavaBeans 属性表示一个数组值使用与该属性相对应的setXXX()

方法和getXXX()方法可以存取数组中某个元素的数值同时我们也可以使用另两个同名

方法一次设置或取得整个数组的值(即属性的值) 请看程序清单1.2

程序清单1.2

//File Name:JavaBean2.java

//Author:fancy

//Date:2001.3.29

//Note:create a indexed javabean

public class JavaBean2

{

int[] dataSet={1 2 3 4 5 6};

public void JavaBean2()

{

}

public void setDataSet(int[] x)

{

dataSet=x;

}

public void setDataSet(int index int x)

{

dataSet[index]=x;

}

public int[] getDataSet()

________

{

return dataSet;

}

public int getDataSet(int x)

{

return dataSet[x];

}

}

在程序清单1.2(JavaBean2.java)中定义了JavaBean JavaBean2 JavaBean2 具有属

性dataSet dataSet 属性是一个整型数组JavaBean2.java 定义了4 个方法以存取dataSet 属

性的值它们分别是 setDataSet(int[] x) setDataSet(int index int x) getDataSet(int x)

getDataSet()其中setDataSet(int[] x)方法可以一次设定dataSet 属性的值getDataSet()方法

可以一次获取dataSet 属性的值该方法的返回值是一个整型数组getDataSet(int x)方法可

以获取dataSet 属性中某个指定的元素的值该方法的返回值为整型数据与这个方法相对

的方法是setDataSet(int index int x)方法使用这个方法可以指定dataSet 属性中某个特定

元素的值

Bound 属性

一个Bound 类型的JavaBean 组件的属性具有这样的特性当该种属性的值发生变化

时必须通知其它的JavaBeans 组件对象每次JavaBeans 组件对象的属性值改变时这种

属性就引发一个PropertyChange 事件(属性改变事件在Java 程序中事件也被看作是一个

对象) 这个事件中封装了发生属性改变事件的属性名属性的原值属性变化后的新值

这个事件将被传递到其它的JavaBeans 组件中至于接收事件的JavaBeans 组件对象应该做

什么动作由其自己定义请看程序清单1.3(JavaBean3.java)

程序清单1.3

//File Name:JavaBean3.java

//Author:fancy

//Date:2001.3.29

//Note:create a bound javabean

import java.beans.*;

public class JavaBean3

{

String ourString= "Hello";

private PropertyChangeSupport changes = new PropertyChangeSupport(this);

public void setString(String newString)

{

String oldString = ourString;

ourString = newString;

changes.firePropertyChange("ourString" oldString newString);

________

第1 章JavaBeans 组件技术

}

public String getString()

{

return ourString;

}

public void addPropertyChangeListener(PropertyChangeListener l)

{

changes.addPropertyChangeListener(l);

}

public void removePropertyChangeListener(PropertyChangeListener l)

{

changes.removePropertyChangeListener(l);

}

}

读者对程序清单1.3(JavaBean3.java)的运行逻辑一定感到十分迷惑吧那好下面我们

就来详细解释JavaBean3.java 程序的含义 程序首先创建了PropertyChangeSupport 类型的

对象changes 这是最关键的一步操作changes 对象主要用于向监听者对象发送信息当

前的JavaBean 对象已经发生了属性改变的事件在JavaBean3.java 程序中除了普通的存

取JavaBeans 属性值的setXXX() getXXX()等方法以外还定义了如下的方法

public void addPropertyChangeListener(PropertyChangeListener l);

public void removePropertyChangeListener(PropertyChangeListener l);

第一个方法(addPropertyChangeListener() 方法) 其实是调用changes 对象的

addPropertyChangeListener()方法使一个事件监听者对象和当前JavaBean 对象绑定起来

并把它添加到监听者队列中去充当当前JavaBean 对象的事件监听者如果当前JavaBean

对象发生了属性值改变的事件那么changes 对象会依次通知监听者队列中的每一个对象

当然也通知了这个事件监听者对象让它对这个事件做出反映

第二个方法(removePropertyChangeListener()方法)和前者的作用相反该方法其实是调

用changes 对象的removePropertyChangeListener()方法从监听者队列中移除某个特定的事

件监听者对象此事件监听者对象一旦从监听者队列中删除那么changes 对象将不会把

属性值改变的事件通知它它再也没有办法对属性值发生改变的事件作出响应了

getString()方法可以返回属性值setString()方法用于设定属性值setString()方法的代

码如下所示

String oldString = ourString;

ourString = newString;

changes.firePropertyChange("ourString" oldString newString);

在上面的代码中首先新定义一个字符串oldString 用于保存属性的原值然后把新

值赋给属性值这样会产生JavaBeans 组件属性值改变的事件最后调用changes 对象的

firePropertyChange()方法通知监听者队列里的所有事件监听者对象当前的JavaBean对

________

第一部分 JSP技术与J2EE技术

象发生了属性值改变的事件属性的名称属性的新值属性的原值都被作为该方法的

参数一并传给监听者对象由它们根据这些信息对此事件作出响应

Bound 类型的属性就是这样使用的

Constrained属性

JavaBeans组件的Constrained 类型的属性具有这样的性质当这个属性的值将要发生

变化但是还没有发生变化的时候与这个属性已经建立了某种监听关系的其它Java 对象可

以否决属性值的改变此Constrained 类型的属性的事件监听者对象将会通过抛出

PropertyVetoException异常事件来阻止该属性值的改变读者请看程序清单

1.4(JavaBean4.java)

程序清单1.4

//File Name:JavaBean4.java

//Author:fancy

//Date:2001.3.29

//Note:create a Constrained javabean

import java.beans.*;

public class JavaBean4

{

private PropertyChangeSupport changes=new PropertyChangeSupport(this);

private VetoableChangeSupport vetos=new VetoableChangeSupport(this);

int ourPriceInCents;

public void setPriceInCents(int newPriceInCents)

throws PropertyVetoException

{

int oldPriceInCents=ourPriceInCents;

vetos.fireVetoableChange("priceInCents"

new Integer(oldPriceInCents)

new Integer(newPriceInCents));

ourPriceInCents=newPriceInCents;

changes.firePropertyChange("priceInCents"

new Integer(oldPriceInCents)

new Integer(newPriceInCents));

}

public void addVetoableChangeListener(VetoableChangeListener l)

{

vetos.addVetoableChangeListener(l);

}

public void removeVetoableChangeListener(VetoableChangeListener l)

________

第1 章JavaBeans 组件技术

{

vetos.removeVetoableChangeListener(l);

}

public void addPropertyChangeListener(PropertyChangeListener l)

{

changes.addPropertyChangeListener(l);

}

public void removePropertyChangeListener(PropertyChangeListener l)

{

changes.removePropertyChangeListener(l);

}

}

程序清单1.4(JavaBean4.java)比起程序清单1.3(JavaBean3.java)来说显得更为晦涩难

解在程序清单1.4 中定义了一个JavaBean JavaBean4 它有一个Constrained 类型的

属性是ourPriceInCents 这是一个整型数据为什么说它是Constrained 类型的属性呢?请读

者注意在程序的开始部分我们分别定义了PropertyChangeSupport 类型的对象changes

和VetoableChangeSupport 类型的对象vetos changes 对象的作用和程序清单1.3 中changes

对象的作用一样在这里我们就不讨论它的用法了在这里我们主要讨论vetos 对象的用法

vetos 对象主要用于通知事件否决者对象某个JavaBean 对象的属性值将要发生变化

让它们投票表决是否允许这个事件的发生在JavaBean4.java 中定义了这样的两个方法

分别是

public void addVetoableChangeListener(VetoableChangeListener l);

public void removeVetoableChangeListener(VetoableChangeListener l);

前者可以往事件否决者对象队列中添加新的事件否决者对象作为JavaBean4 组件对

象的事件否决者一旦成为JavaBean4 对象的事件否决者就可以在事件发生之前否决

事件的发生

第二个方法与第一个方法的作用相反它可以将某个特定的事件否决者对象从事件否

决者对象列表中删除被删除的事件否决者对象就再也没有权利否决事件的发生除非它

们再次被添加到事件否决者队列中去

在JavaBean4.java 程序中读者需要特别注意setPriceInCents()方法的实现在

setPriceInCents()方法中首先把ourPriceInCents 属性的原值给保存下来然后调用vetos

对象的fireVetoableChange()方法通知事件否决者对象队列中的每一个事件否决者对象

告诉它们JavaBean4 对象即将发生属性改变的事件发生此事件的属性是ourPriceInCents

属性的新值为newPriceInCents 属性的原值为oldPriceInCents (实际上还没有改变属性值)

事件否决者对象会根据这些信息投票表决是否允许该事件的发生如果有任何一个事件

否决者对象否决了这个事件发生的可能性那么setPriceInCents() 方法将会抛出

PropertyVetoException 异常程序的运行将会中断下面的代码将不会执行也就是说属性

值将会保持原来的值如果事件否决者不否决事件的发生那么程序将会继续往下执行

给ourPriceInCents 属性赋上新值然后changes 对象调用firePropertyChange()方法通知事

件监听者队列中的事件监听者对象让它们对这个事件作出响应

总之某个JavaBean 组件对象的Constrained 类型的属性值可否改变取决于其它的事

件否决者对象是否允许这种改变允许与否的条件由其它的事件否决者对象在自己的类中

进行定义

注意事件监听者和事件否决者的区别在于事件监听者不能够否决事件的发生但是

可以响应事件的发生而事件否决者正好相反它可以否决事件的发生但是

不能够响应事件的发生

1.1.3 JavaBeans 的事件模型

事件处理机制是JavaBeans 体系结构的核心之一也是Java 体系结构的核心之一通

过事件处理机制我们可以指定一些组件作为事件源发出可以被系统运行环境或者是其

它组件接收的事件这样不同的组件就可在某个应用程序内部真正结合在一起组件之

间通过事件的发送传递接受进行通信构成一个完整的逻辑应用从概念上讲所谓

事件机制是指一种在源对象和监听者对象之间某种状态发生变化时的消息传

递机制事件有许多不同的用途例如在Windows 系统中常要处理的鼠标事件窗口边界

改变事件键盘事件等在Java 和JavaBeans 的事件模型中则是定义了一个一般的可

扩充的事件机制这种机制能够

对事件类型和传递的模型的定义和扩充提供一个公共框架并适合于广泛的应用

与Java 语言和环境有较高的集成度

事件能被系统运行环境捕获和引发

能使其它开发工具采取某种技术在设计时直接控制事件以及事件源和事件监听

者事件否决者之间的联系

事件机制本身不依赖于复杂的开发工具

特别地还应当

能够发现指定的对象类可以生成的事件

能够发现指定的对象类可以观察监听到的事件

提供一个常规的注册机制允许动态操纵事件源与事件监听者之间的关系

不需要其它的虚拟机和语言即可实现

事件源与监听者之间可进行高效快速的事件传递

下面我们就来简单地介绍JavaBeans 的事件机制是如何运作的

事件模型概述

事件从事件源到事件监听者的传递是通过对监听者对象的Java 方法调用进行的 对

每个明确的事件的发生都必须相应地定义一个明确的Java 方法这些方法都集中在事件

监听者接口中定义而且这个接口必须要继承java.util.EventListener 接口也就是说如果

我们希望监听事件源发生的事件我们必须首先定义一个事件监听者接口定义各种各样

的监听方法以便接收事件源传递来的事件具体实现了事件监听者接口中一些或全部方

第1 章JavaBeans 组件技术

法的类就是事件监听者伴随着事件的发生事件源通常把事件及其相应的状态都封装在

事件状态对象中该对象必须继承自java.util.EventObject 事件状态对象作为参数被传递

给应该响应该事件的事件监听者的方法中

产生某种特定事件的事件源的特征是遵从规定的编程格式为事件监听者定义注册方

法以便把监听者对象加入当前事件源的事件监听者队列中并接受对指定事件监听者接

口实例的引用有时事件监听者不能直接实现事件监听者接口或者还有其它的额外动

作时就要在一个事件源与其它一个或多个事件监听者之间插入一个事件适配器类的实例

对象来建立它们之间的联系实际上事件适配器类就相当于一个过滤器它可以把事

件监听者对象不应该接收的事件或者是不能够接收的事件都过滤掉

事件状态对象Event State Object

与事件有关的状态信息一般都封装在一个事件状态对象中这种对象必须是

java.util.EventObject 类的子类按设计习惯这种事件状态对象类的名应以Event 结尾请

看程序清单1.5 (MouseMovedExamEvent.java)

程序清单1.5

//File Name: MouseMovedExamEvent

//Author:fancy

//Date:2001.3.31

//Note:EventObject-----Mouse Moved Event

import java.awt.Point;

public class MouseMovedExamEvent extends java.util.EventObject

{

protected int x;

protected int y;

public void MouseMovedExampleEvent(Component source Point location)

{

super(source);

x = location.x;

y = location.y;

}

public Point getLocation()

{

return new Point(x y);

}

}

在程序清单1.5(MouseMovedExamEvent.java)中我们定义了一个事件状态对象

MouseMovedExampleEvent 它代表一个鼠标移动的事件getLocation()方法可以返回鼠标

目前的位置

________

事件监听者接口与事件监听者

由于JavaBeans 的事件模型是基于Java 的方法调用因而需要一个定义并组织事件操

纵方法的方式在JavaBeans 事件模型中事件操纵方法都被定义在继承了

java.util.EventListener 接口的事件监听者接口中按照一般的规律事件监听者接口的命名

要以Listener 结尾任何一个类如果想使用在事件监听者接口中定义的方法都必须扩展这

个接口并且实现其中定义的方法果真如此那么这个类也就是事件监听者请看程序

清单1.6(ArbitraryObject.java)

程序清单1.6

//File Name: ArbitraryObject.java

//Author:fancy

//Date:2001.3.31

//Note: show JavaBean event model

import java.beans.*;

//定义事件状态对象类

public class MouseMovedExampleEvent extends java.util.EventObject

{

// 在此类中包含了与鼠标移动事件有关的状态信息

}

//定义了鼠标移动事件的事件监听者接口

interface MouseMovedExampleListener extends java.util.EventListener

{

//在这个接口中定义了鼠标移动事件监听者所应支持的方法

void mouseMoved(MouseMovedExampleEvent mme);

}

//定义事件监听者

class ArbitraryObject implements MouseMovedExampleListener

{

public void mouseMoved(MouseMovedExampleEvent mme)

{

//代码省略

}

在程序清单1.6(ArbitraryObject.java) 中首先定义了事件状态对象类

MouseMovedExampleEvent 在此类中包含了与鼠标移动事件有关的状态信息接着定义了

事件监听者接口MouseMovedExampleListener 在这个接口中定义了鼠标移动事件监听者所

应支持的方法mouseMoved() 该方法以MouseMovedExampleEvent 类型的对象为参数

ArbitraryObject 类扩展了MouseMovedExampleListener 接口实现了mouseMoved 方法所

________

第1 章 JavaBeans 组件技术

以它是事件监听者

注意程序清单1.5/1.6 只是简单的示例代码不完整编译不会通过也不能够运行

读者务必要注意

事件监听者的注册与注销

为了把各种可能的事件监听者注册到合适的事件源的监听者队列中建立事件源与事

件监听者间的事件流事件源必须为事件监听者提供注册和注销的方法在前面介绍bound

类型的JavaBeans 属性时我们已经提到了这两种方法在实际编程中事件监听者的注

册和注销方法必须使用标准的设计格式

public void add< ListenerType>(< ListenerType> listener)

public void remove< ListenerType>(< ListenerType> listener)

前者用于注册事件监听者对象后者用于注销事件监听者对象ListenerType 代表事

件监听者对象的类型

程序清单1.7

//File Name:EventExam.java

//Author:fancy

//Date:2001.3.31

//Note:show JavaBean event model

import java.util.*;

//首先定义了一个事件监听者接口

public interface ModelChangedListener extends java.util.EventListener

{

void modelChanged(EventObject e);

}

//定义事件监听者

class ModelChangedEventObject implements ModelChangedListener

{

public void modelChanged(EventObject e)

{

//代码省略

}

//接着定义事件源类

public class EventExam

{

// 定义了一个储存事件监听者的数组

private Vector listeners = new Vector();

________

//上面设计格式中的<ListenerType>在此处即是下面的ModelChangedListener

//把监听者注册入listeners数组中

public void addModelChangedListener(ModelChangedListener mcl)

{

listeners.addElement(mcl);

}

//把监听者从listeners中注销

public void removeModelChangedListener(ModelChangedListener mcl)

{

listeners.removeElement(mcl);

protected void notifyModelChanged()

{

//事件源对象使用本方法通知监听者发生了modelChanged事件

Vector l;

EventObject e = new EventObject(this);

//首先要把监听者拷贝到l数组中冻结EventListeners的状态以传递事件

//这样来确保在事件传递到所有监听者之前已接收了事件的目标监听者的对

//应方法暂不生效

synchronized(this)

{

l = (Vector)listeners.clone();

}

for (int i = 0; i < l.size(); i++)

{

//依次通知注册在监听者队列中的每个事件监听者发生了modelChanged

//事件并把事件状态对象e作为参数传递给监听者队列中的每个监听者

((ModelChangedListener)l.elementAt(i)).modelChanged(e);

}

}

}

在程序清单1.7(EventExam.java)中展示了一个完整的JavaBeans 事件模型程序首先

定义了事件监听者接口ModelChangedListener 然后又定义了一个事件监听者

ModelChangedEventObject ModelChangedEventObject 类实现了ModelChangedListener 接口

中定义的modelChanged()方法由于该事件监听者所监听的是最普遍的事件对象

EventObject 因此我们就不必定义事件状态对象了接下来我们定义了事件源EventExam

________

第1 章JavaBeans 组件技术

类EventExam 类使用Vector 数据类型来存储事件监听者队列EventExam 类定义了

addModelChangedListener()方法用来往事件监听者队列中添加事件监听者对象(表面上添加

的是事件监听者接口ModelChangedListener 对象但在实际上添加的是事件监听者对象

ModelChangedEventObject) removeModelChangedListener()方法可以把事件监听者队列中的

特定的事件监听者对象注销事件源对象调用notifyModelChanged()方法通知事件监听者发

生了modelChanged 事件notifyModelChanged()方法的方法体中使用一个for 循环结构

遍历Vector 数据结构中保存的每一个事件监听者接口对象调用它们的modelChange()方

法通知事件监听者modelChanged 事件已经发生了并且把事件状态对象e 传递给这些

事件监听者这里虽然调用的是ModelChangedListener 接口的modelChange()方法但是这

个方法并没有真正实现所以实际上调用的是ModelChangedEventObject 类的modelChange()

方法

事件适配器类

事件适配器类是Java JavaBeans 事件模型中极其重要的一部分在一些应用场合

事件从事件源到事件监听者之间的传递要通过事件适配器类来转发例如当事件源发

出一个事件而有几个事件监听者对象都可接收该事件但只有指定的监听者对象可以做

出反应时就要在事件源与事件监听者之间插入一个事件适配器类由适配器类来指定事

件应该是由哪些事件监听者来响应再由它来转发事件

注意JavaBeans的事件模型实际上用的并不多尤其是应用于JSP程序中的JavaBeans

很少需要响应或者监听某种事件的产生但是这并不等于这部分的内容不重要

有时候为了纪录JavaBeans 都作了哪些敏感的操作还是需要利用JavaBeans 的

事件模型的

1.2 JSP 中如何使用JavaBeans

JavaBeans 被称为是Java 组件技术的核心JavaBeans 的结构必须满足一定的命名约

定JavaBeans 类似于Windows 下的ActiveX 控件它们都能提供常用功能并且可以重复

使用JavaBeans 可以在JSP 程序中应用给我们带来了很大的方便这使得开发人员可

以把某些关键功能和核心算法提取出来封装成为一个组件对象增加了代码的重用

率系统的安全性比如我们可以将访问数据库的功能数据处理功能编写封装为

JavaBeans 组件然后在某个JSP 程序中加以调用JavaBeans 技术与ActiveX 相比有

着很大的优越性例如JavaBeans 的与平台无关性使得JavaBeans 组件不但可以运行

于Unix平台还可以运行在Windows 平台下面而且JavaBeans 从一个平台移植到另

外的平台上代码不需要修改甚至不需要重新编译但是ActiveX 就不同了它只能

够应用于Windows 平台而且它的代码移植性很差从Windows 98 平台移植到NT 平

台就需要重新编译代码甚至要大幅度改写程序另一方面JavaBeans 比ActiveX 要

容易编写得多用起来也方便得多起码JavaBeans 组件在使用以前不需要注册而

ActiveX 控件在使用以前必须在操作系统中注册否则在运行的时候系统将会报错

本节将介绍在JSP 程序中如何使用JavaBeans 组件要想在JSP 程序中使用

JavaBeans 组件必须应用<jsp:useBean> <jsp:setProperty> <jsp:getProperty>等JSP 的操

作指令关于这几个操作指令的用法我们在《JSP深入編程》中已经有所涉及但是限于

体系结构方面的原因我们的讨论十分肤浅而且没有举出具体的例子这不能不说是一

个缺憾在这一节中我们会结合实际的例子再次详细介绍这三个操作指令的用法顺

便帮助读者复习一下JSP 的基础知识

1.2.1 <jsp:useBean>操作指令

<jsp:useBean>操作指令用于在JSP 页面中实例化一个JavaBean 组件这个实例化的

JavaBean 组件对象将可以在这个JSP 程序的其它地方被调用<jsp:useBean>操作指令的基

本语法形式如下所示

<jsp:useBean id="name" scope="page|request|session|application" typeSpec />

或者

<jsp:useBean id="name" scope="page|request|session|application" typeSpec />

body

</jsp:useBean>

语法参数描述

id 属性用来设定JavaBeans 的名称利用id 可以识别在同一个JSP 程序中使用

的不同的JavaBeans 组件实例

class 属性指定JSP 引擎查找JavaBeans 代码的路径一般是这个JavaBean 所对应

的Java 类名

scope 属性用于指定JavaBeans 实例对象的生命周期亦即这个JavaBean 的有效作

用范围scope 的值可能是page request session 以及application 在下面1.3 节

中我们会详细讨论这四个属性值的含义与用法

typeSpec 可能是如下的四种形式之一

class="className"

或者

class="className" type="typeName"

或者

beanName="beanName" type=" typeName"

或者

type="typeName"

当JavaBeans 组件对象被实例化以后你就可以访问它的属性来定制它我们要获得

它的属性值应当使用<jsp:getProperty>操作指令或者是在JSP 程序段中直接调用JavaBeans

对象的getXXX()方法<jsp:getProperty>操作指令的语法形式如下所示

<jsp:getProperty id="Name" property="name" />

使用这个操作指令可以获取将要用到的JavaBeans 组件实例对象的属性值实际的值

将会被放在输出语句中

要改变JavaBeans 的属性你必须使用<jsp:setProperty>操作指令或者是直接调用

JavaBeans 对象的方法<jsp:setProperty>操作指令有以下两种语法形式

________

第1 章JavaBeans 组件技术

<jsp:setProperty id="Name" property="*" />

或者

<jsp:setProperty id="Name" property="propertyNumber" value="string" />

前者的功能是根据已提交的表单中的数据设置这个JavaBean 中相应(JavaBeans 属性

的名称和表单对象的名称要相同)的属性值后者的功能是把这个JavaBeans 的指定的属性

设为指定的值

为了能在JSP 程序中使用JavaBeans 组件你需要特别注意JavaBeans 类程序的存放问

题:为了使应用程序服务器能找到JavaBeans 类你需要将其类文件放在Web 服务器的一个

特殊位置以JSWDK1.0.1 服务器为例JavaBeans 的类文件(编译好的class 文件)应该放在

examples\WEB-INF\jsp\beans 目录下或者是webpages\WEB-INF\jsp\beans 目录下面在resin

服务器中则是放在doc\WEB-INF\classes 目录下的至于JavaBeans 在其他服务器下的存放

路径读者可以参考下文的介绍或者相应服务器的开发文档

1.2.2 <jsp:setProperty>操作指令

<jsp:setProperty>操作指令被用于指定JavaBeans 的某个属性的值它的语法形式如下

所示:

<jsp:setProperty name="BeanName" PropertyExpr />

PropertyExpr ::= property="*"|

property="PropertyName"|

property="PropertyName" value="PropertyValue"|

property="PropertyName" param="ParameterName"|

语法参数说明

name name 属性用来指定JavaBeans 的名称这个JavaBeans 必须首先使用

<jsp:useBean>操作指令来实例化

property property 属性被用来指定JavaBeans 需要定制的属性的名称如果property

属性的值为* 那么会发生什么情况呢?请参考1.4.2 小节

value value 属性的值将会被赋给JavaBeans 的属性

param param 这个属性的作用很微妙如果客户端传递过来的参数中有一个参

数的名字和param 属性的值相同那么这个参数的值将会被赋给JavaBean 的属性

所以使用了param 属性就不要使用value 属性反之使用了value 属性就不要使

用param 属性这两个属性是互斥的不过param 属性必须和property 属性搭配

使用否则就不知道该赋值给JavaBeans 的哪一个属性了

我们不提倡读者使用<jsp:setProperty>操作指令而应该在JSP 程序段中直接调用

JavaBeans 组件实例对象的setXXX()方法因为后者的代码简单使用起来比较灵活相

对而言前一种方法的代码就比较繁琐了而且灵活性也不好以param 属性为例客户

端传递归来的参数值一般不应该直接赋给JavaBeans 的属性而应该先转换汉字的内码

再赋值这一点上param 属性就无能为力了

________

第一部分 JSP 技术与J2EE 技术

1.2.3 <jsp:getProperty>操作指令

<jsp:getProperty>操作指令搭配<jsp:useBean>操作指令一起使用可以获取某个

JavaBean 组件对象的属性值并使用输出方法将这个值输出到页面<jsp:getProperty>操作

指令的语法形式如下所示

<jsp:getProperty name=”BeanName” Property=”PropertyName” />

语法参数说明

name 这个属性用来指定JavaBeans 的名称这个JavaBeans 组件对象必须已经使

用<jsp:useBean>操作指令实例化了

Property Property 用来指定要读取的JavaBeans 组件对象的属性的名称

实际上我们也可以在JSP 程序段中直接调用JavaBeans 对象的getXXX()方法来获

取JavaBeans 对象的属性值我们觉得使用这个方法要比使用<jsp:getProperty>操作指令好

因为前者使用起来比较灵活而且代码相对比较简单

1.2.4 JavaBeans 的开发流程

在这一小节里我们将详细讨论如何开发JavaBeans 组件如何把它用到JSP 程序的

开发中去实现一个完整的JavaBeans+JSP 的开发流程

编写JavaBeans 组件

第一步应该是编写一个JavaBeans 组件程序我们这就根据上面介绍的知识编写

一个十分简单的JavaBeans 程序请看程序清单1.8(HelloWorld.java)

程序清单1.8

//File Name:HelloWorld.java

//Author:fancy

//Date:2001.3.26

//Note:use this JavaBean to say hello world!

package test;

public class HelloWorld

{

String Hello="hello world I am fancy!";

public void HelloWorld()

{

}

public void setHello(String name)

{

________

第1 章 JavaBeans 组件技术

Hello=name;

}

public String getHello()

{

return Hello;

}

}

在程序清单1.8(HelloWorld.java) 中我们编写了一个十分简单的JavaBean

HelloWorld 它有一个字符串类型的Hello 属性用于保存问候信息在编写HelloWorld.java

程序时要注意HelloWorld 类必须显式声明为public 类型其次是package 语句的使用

请看代码行:

package test;

这一行代码指示编译器把编译好的类作为test 包的一部分HelloWorld.class(类文件)

HelloWorld.java(程序文件)文件必须位于test 文件夹中

编译HelloWorld.java 程序

编写好HelloWorld.java 程序以后我们应该把它保存到哪里呢?以JSWDK1.0.1 服务器

为例应该把它保存到webpages\WEB-INF\jsp\beans\目录下面我们必须新建一个文件夹

这个文件夹的名字必须和package 语句所指定的包名相同否则服务器无法找到JavaBean

的类代码在本例中这个文件夹的名字应该是test 保存好HelloWorld.java 程序后使

用javac.exe 程序把它编译为class 文件

编写JSP 程序

第三步是编写JSP 程序调用我们在上面的步骤中编写好的HelloWold 组件请看程

序清单1.9(useBean.jsp)

程序清单1.9

<%--

File Name:useBean.jsp

Author:fancy

Date:2001.3.26

Note:use javabean to say hello world!

--%>

<jsp:useBean id="hello" scope="page" class="test.HelloWorld" />

<jsp:getProperty name="hello" property="Hello" />

<br>

<%

hello.setHello("Are you want to talk to me?");

%>

________

第一部分 JSP 技术与J2EE 技术

<%=hello.getHello()%>

在程序清单1.9(useBean.jsp)中首先使用<jsp:useBean>操作指令实例化了HelloWorld

组件对象在下面的代码中就可以使用hello 来引用HelloWorld 组件对象读者应该注

意class 属性设为test.HelloWorld 其中HelloWorld 代表类的名字test 有两重含义第一

HelloWorld 类属于test 包第二HelloWorld 类文件保存在test 文件夹中所以package 语

句指定的包名保存JavaBeans 类的目标文件夹名还有<jsp:useBean>操作指令中class 属

性值点号前的部分这三个值一定要完全相同才行否则JSP 服务器都将不能找到相应的

JavaBeans 类

接下来使用<jsp:getProperty>操作指令获取HelloWorld 组件对象Hello 属性的值并把

它输出然后在JSP 程序段和JSP 表达式中分别调用hello 对象的setHello()方法和getHello()

方法设定和获取该对象Hello 属性的值

程序清单1.9 的运行效果如图1.1 所示

图1.1 useBean.jsp 程序的运行效果

到此为止一个完整的使用了JavaBeans 组件的JSP 项目就算开发成功了这个开发

流程虽然简单不过凡是需要用到JavaBeans 组件的JSP 程序的开发一般都应该遵循这

个流程进行开发

1.2.5 JavaBeans 的保存路径

在这一个小节中我们将总结JavaBeans 程序在不同的JSP 服务器平台中的保存路径

在介绍这些知识以前我们首先讨论JavaBeans 程序的存储格式

JavaBeans 组件被设计出来后一般是以扩展名为jar 的压缩格式文件存储在jar 文

件中包含了与JavaBeans 有关的信息并以MANIFEST 文件指定其中的哪些类是JavaBeans

的类文件以jar 文件格式存储的JavaBeans 程序在网络中传送时极大地减少了数据的传输

数量并把JavaBeans 运行时所需要的一些资源捆绑在一起jar 文件其实是一个zip 文件

把它的扩展名改为zip 就可以使用Winzip 程序打开它如何才能创建jar 文件呢?答案是

使用JDK 的jar.exe 程序jar.exe 程序一般位于JDK 的bin 目录下面这是一个命令行程序

它的用法如下

jar {ctxu}[vfm0M] [jar-文件] [manifest-文件] [-C 目录] 文件名 ...

选项

________

第1 章 JavaBeans 组件技术

-c 创建新的归档

-t 列出归档内容的列表

-x 展开归档中的命名的或所有的文件

-u 更新已存在的归档

-v 生成详细输出到标准输出上

-f 指定归档文件名

-m 包含来自指定的清单manifest 文件的清单manifest 信息

-0 只存储方式未用ZIP 压缩格式

-M 不产生所有项的清单manifest 文件

-i 为指定的jar 文件产生索引信息

-C 改变到指定的目录并且包含下列文件

如果一个文件名是一个目录它将被递归处理

清单manifest 文件名和归档文件名都需要被指定按'm' 和 'f'标志指定的

相同顺序

示例1 将两个class 文件归档到一个名为 'classes.jar' 的归档文件中

jar cvf classes.jar Foo.class Bar.class

示例2 用一个存在的清单manifest 文件 'mymanifest' 将 foo/ 目录下的所有

文件归档到一个名为 'classes.jar' 的归档文件中

jar cvfm classes.jar mymanifest -C foo/ .

下面我们总结JavaBeans 类在不同的JSP 服务器平台下面的保存位置

JSWDK1.0.1 服务器

保存路径为

1 webpages\WEB-INF\jsp\beans\folderName

2 examples\WEB-INF\jsp\beans\folderName

JRun 3.0 服务器

保存路径为

1 servers\default\default-app\WEB-INF\classes\folderName

2 servers\default\demo-app\WEB-INF\classes\folderName

Tomcat 3.1/3.2 服务器

保存路径为

1 webapps\admin\WEB-INF\classes\folderName

2 webapps\examples\WEB-INF\classes\folderName

3 webapps\ROOT\WEB-INF\classes\folderName

4 webapps\test\WEB-INF\classes\folderName

5 webapps\Xmlservlet\WEB-INF\classes\folderName

________

第一部分 JSP 技术与J2EE 技术

Resin1.2 服务器

保存路径为

1 doc\WEB-INF\classes\folderName

2 doc\examples\..\WEB\INF\classes\folderName

限于篇幅关于JavaBeans 程序在各个JSP 服务器平台下的保存路径我们就介绍到

这里如果读者还希望了解JavaBeans 程序在其他JSP 服务器平台下的保存路径请参考

相应的服务器开发文档或者是与本书作者联系

1.3 JavaBeans 的Scope 属性

对于JSP 程序而言使用JavaBeans 组件不仅可以封装许多信息而且还可以将一些

数据处理的逻辑隐藏到JavaBeans 的内部除此之外我们还可以设定JavaBeans 的Scope

属性使得JavaBeans 组件对于不同的任务具有不同的生命周期和不同的使用范围在

前面我们已经提到过Scope 属性具有四个可能的值分别是application session request

page 分别代表JavaBeans 的四种不同的生命周期和四种不同的使用范围下面我们就分别

介绍这四种不同的情况

1.3.1 Application Scope

如果JavaBeans 的Scope 属性被指定为application 也就是说这个JavaBean 组件具有

Application Scope 这是什么意思呢?如果一个JavaBean 组件具有Application Scope 那么

它的生命周期和JSP 的Application 对象同步作用范围也和Application 对象一样使用这

种类型的JavaBeans 组件可以在多个用户之间共享全局信息具体来说它的生命周期

是这样子的如果某个JSP 程序使用<jsp:useBean>操作指令创建了一个JavaBean 对象而

且这个JavaBean 组件具有Application Scope 那么这个JavaBean 就一直在服务器的内存空

间中待命随时处理客户端的请求直到服务器关闭为止它所保存的信息才消失它所

占用的系统资源才会被释放 在此期间如果有若干个用户请求的JSP 程序中需要用到

这个JavaBean 组件那么服务器在执行<jsp:useBean>操作指令时并不会创建新的JavaBean

组件而是创建源对象的一个同步拷贝在任何一个拷贝对象上发生的改变都会影响到源

对象源对象也会做出同步的改变不过这个状态的改变不会影响其他已经存在的拷贝对

象这种类型的JavaBeans 组件的功能和JSP 的Application 对象十分类似不过前者的功

能要强大得多而且可以自由扩展用起来也方便得多请看程序清单1.10(Counter.java)

程序清单1.11(useCounter.jsp)

程序清单1.10

//File Name:Counter.java

//Author:fancy

//Date:2001.3.26

//Note:use this JavaBean to Counter!

package test;

________

第1 章 JavaBeans 组件技术

public class Counter

{

int Count=1;

public void Counter()

{

}

public void addCount()

{

Count++;

}

public int getCount()

{

return Count;

}

}

在程序清单1.10 中我们定义了一个Counter Bean 这个JavaBean 组件可以用于记录

访问者的人数由于这个程序十分简单我们就不多做介绍了

程序清单1.11

<%--

File Name:useCounter.jsp

Author:fancy

Date:2001.3.26

Note:use javabean to say hello world!

--%>

<jsp:useBean id="counter" scope="application" class="test.Counter" />

<br>

你好你是第

<%

out.println(counter.getCount());

counter.addCount();

%>位访客

程序清单1.11(useCounter.jsp)中首先使用<jsp:useBean>操作指令引入了JavaBean 组

件Counter 并且声明它的Scope 为Application 这一步十分重要然后调用Counter

组件的getCount()方法获取访问过这个JSP 程序的人数如果Counter 组件刚刚被创建

那么这个方法将会返回缺省值1 接着调用Counter 组件的addCounte()方法把访问人

数加上1

________

第一部分 JSP 技术与J2EE 技术

程序清单1.11 的运行效果如图1.2 所示

图1.2 useCounter.jsp 的运行效果

1.3.2 Session Scope

如果一个JavaBean 组件的Scope 属性值为session 那么这个JavaBean 组件的生命周

期作用范围就和JSP 的Session 对象的生命周期作用范围一样也就是说这一类型

的JavaBeans 组件的生命周期就是某个会话过程所经历的时间也许有的读者对会话过程

还不太了解实际上会话过程是对于单个用户而言的会话过程的开始以用户开始访问

某个网站为标志会话过程的结束以用户结束对该网站的访问为标志不同的用户对应着

不同的会话过程不同的会话过程之间互不干涉互不影响假设用户A 第一次访问了某

个网站的某个JSP 程序而这个JSP 程序用到了一个Scope 属性为session 的JavaBean 组

件那么服务器会自动创建这个JavaBean 组件的实例对象并且当A 用户继续访问同一网

站其他的JSP 程序而其他的JSP 程序又用到同一个JavaBean 对象时那么服务器不会创

建新的JavaBean 对象而是使用已经存在的JavaBean 对象实例也就是说在第一个JSP

程序中创建的JavaBean 组件对象在这个用户访问的同一网站的所有的JSP 程序中都是可用

的而且这个JavaBean 组件对象的状态保持唯一性如果有另一个用户B 访问了用户A

访问过的JSP 程序那么服务器是否会不创建新的JavaBean 组件对象而使用由于用户A

访问而创建的JavaBean 组件对象呢?答案是否定的服务器将会为用户B 创建只属于他的

JavaBean 组件对象这个新创建的JavaBean 组件对象在用户B 访问的同一网站的所有JSP

程序中都是直接可用的而不需要创建一个新的组件并且属于用户A 的JavaBean 组件对

象和属于用户B 的组件对象都是唯一的它们之间互不干涉这里我们讨论的只是两个用

户的情况其实如果有多个用户在线情况也一样

综上所述Scope 属性为session 的JavaBeans 组件的功能作用范围都和JSP 的Session

对象十分类似不过前者的功能比后者要强大得多并且使用起来也灵活得多具有可扩

展性后者没有扩展性

下面我们就利用这种类型的JavaBeans 组件来编写一个特殊的计数器程序这个计

数器并不是统计一个网页的访问人数而是统计一个用户所访问的页面数目请看程序清

单1.12(beanPage1.jsp) 程序清单1.13(beanPage2.jsp)

程序清单1.12

<%--

File Name:beanPage1.jsp

________

第1 章 JavaBeans 组件技术

Author:fancy

Date:2001.3.26

Note:use Counter to calculate how many pages this user have visited

--%>

<jsp:useBean id="counter" scope="session" class="test.Counter" />

<br>

第一页

<br>

你好你已经访问了

<%

out.println(counter.getCount());

counter.addCount();

%>个页面

在程序清单1.12 中我们使用的JavaBean 组件仍然是我们在程序清单1.10 中编写的

Counter 不过这里Counter 对象的Scope 属性值是session 而不是Application 当用户首

先调用beanPage1.jsp 程序时Counter 对象被创建了程序清单1.12 的运行效果如图1.3

所示

图1.3 beanPage1.jsp 程序的运行效果

程序清单1.13

<%--

File Name:beanPage2.jsp

Author:fancy

Date:2001.3.26

Note:use Counter to calculate how many pages this user have visited

--%>

<jsp:useBean id="counter" scope="session" class="test.Counter" />

<br>

第二页

<br>

你好你已经访问了

<%

________

第一部分 JSP 技术与J2EE 技术

out.println(counter.getCount());

counter.addCount();

%>个页面

程序清单1.13(beanPage2.jsp)程序与程序清单1.12 基本上是一样的如果我们首先调

用了程序清单1.12 再访问程序清单1.13 那么服务器在执行<jsp:useBean>操作指令时

只是返回在程序清单1.12 中创建的Counter 对象而不会创建新的Counter 对象(即使两个

程序文件中的<jsp:useBean>操作指令的id 属性值不同也不会创建新的JavaBean 对象)

程序清单1.13(beanPage2.jsp)的运行效果如图1.4 所示

图1.4 beanPage2.jsp 程序的运行效果

请读者想一想如果首先调用beanPage2.jsp 程序再调用beanPage1.jsp 程序那么

运行效果还会是这样吗?如果不是那么又应该如何呢?读者不妨试一试

1.3.3 Request Scope

如果JavaBeans 的Scope 属性值被设为request 那么这种类型的JavaBeans 组件对象

又有何特性呢?可能读者已经猜到了这种类型的JavaBeans 组件对象的生命周期和作用范

围和JSP 的Request 对象一样当一个JSP 程序使用<jsp:forward>操作指令定向到另外一个

JSP 程序或者是使用<jsp:include>操作指令导入另外的JSP 程序那么第一个JSP 程序会把

Request 对象传送到下一个JSP 程序而属于Request Scope 的JavaBeans 组件对象也将伴随

着Request 对象送出被第二个JSP 程序接收因此所有通过这两个操作指令连接在一

起的JSP 程序都可以共享一个Request 对象共享这种类型的JavaBeans 组件对象这种类

型的JavaBeans 组件对象使得JSP 程序之间传递信息更为容易不过美中不足的是这种

JavaBeans 不能够用于客户端与服务端之间传递信息因为客户端是没有办法执行JSP 程序

创建新的JavaBeans 对象的

下面的程序清单1.14(RequestBean.java) 程序清单1.15(beanPage3.jsp)和程序清单

1.16(beanPage4.jsp)演示了这种JavaBeans 组件对象的使用方法

程序清单1.14

//File Name:RequestBean.java

//Author:fancy

//Date:2001.3.26

//Note:use this JavaBean to transfer info between two jsp program

________

第1 章 JavaBeans 组件技术

package test;

public class RequestBean

{

String url="index.jsp";

public void Counter()

{

}

public void setURL(String strURL)

{

url=strURL;

}

public String getURL()

{

return url;

}

}

在程序清单1.14(RequestBean.java)中创建的RequestBean Bean 可以用于保存当前页面

的名称缺省值为index.jsp 在下面的程序清单1.15(beanPage3.jsp) 和程序清单

1.16(beanPage4.jsp)中将要使用这个JavaBean 组件实现一个小功能

程序清单1.15

<%--

File Name:beanPage3.jsp

Author:fancy

Date:2001.3.26

Note:use RequestBean to pass info before two different jsp page

--%>

<jsp:useBean id="reqBean" scope="request" class="test.RequestBean" />

调用页:beanPage3.jsp

<%

reqBean.setURL("beanPage3.jsp");

%>

<br>

<br>

<jsp:include page="beanPage4.jsp" flush="true" />

在程序清单1.15 中(beanPage3.jsp) 首先使用<jsp:useBean>操作指令创建一个新的

RequestBean 组件对象reqBean 然后在JSP 程序段中使用setURL()方法把当前程序的名称

________

第一部分 JSP 技术与J2EE 技术

beanPage3.jsp 保存在reqBean 对象的url 属性中接下来使用<jsp:include>操作指令导入

beanPage4.jsp 程序

程序清单1.16

<%--

File Name:beanPage4.jsp

Author:fancy

Date:2001.3.26

Note:use RequestBean to pass info before two different jsp page

--%>

<jsp:useBean id="reqBean" scope="request" class="test.RequestBean" />

被调用页:beanPage4.jsp

<br>

本页面由

<%

out.println(reqBean.getURL());

%>

调用

在程序清单1.16(beanPage4.jsp) 中同样使用<jsp:useBean> 操作指令以便获取

RequestBean 组件对象的实例由于在执行beanPage3.jsp 程序的时候已经创建了

RequestBean 组件的实例对象所以在这里就不用创建新的RequestBean 对象简单地引用

已经存在的组件对象即可然后在JSP 程序段中调用RequstBean 组件的getURL()方法

获取使用<jsp:include>操作指令的JSP 程序的名称在本例中这个值应该是beanPage3.jsp

程序清单1.15(beanPage3.jsp)的运行效果如图1.5 所示

图1.5 beanPage3.jsp 程序的运行效果

如果不事先运行beanPage3.jsp 而是直接运行beanPage4.jsp 程序那么运行效果应该

如何呢?根据我们前面介绍的知识在执行beanPage4.jsp 程序中的<jsp:useBean>操作指令

时由于beanPage4.jsp 没有接受到现存的RequestBean 组件对象那么JSP 服务器将会创

建一个新的RequestBean 对象接下来调用getURL()方法的时候由于RequestBean 的

url 属性未经重新赋值所以getURL()方法的执行结果是返回缺省值index.jsp 程序清单

1.16(beanPage4.jsp)的运行效果如图1.6 所示

________

第1 章 JavaBeans 组件技术

图1.6 beanPage4.jsp 程序的运行效果

1.3.4 Page Scope

如果一个JavaBean 的Scope 属性被设为page 那么它的生命周期和作用范围在这四种

类型的JavaBean 组件中是最小的Page Scope 类型的JavaBeans 组件的生命周期为JSP 程

序的运行周期当JSP 程序运行结束那么该JavaBean 组件的生命周期也就结束了Page

Scope 类型的JavaBeans 组件程序的作用范围只限于当前的JSP 程序中它无法在别的JSP

程序中起作用对应于不同的客户端请求服务器都会创建新的JavaBean 组件对象而且

一旦客户端的请求执行完毕那么该JavaBean 对象会马上注销无法为别的客户端请求所

使用

下面的程序清单1.17(JspCalendar.java) 程序清单1.18(date.jsp)演示了Page Scope 的

JavaBeans 组件的用法

程序清单1.17

//File Name:JspCalendar.java

//Author:fancy

//Date:2001.3.26

//Note:use this JavaBean to get current time

package test;

import java.text.DateFormat;

import java.util.*;

public class JspCalendar

{

Calendar calendar = null;

public JspCalendar()

{

calendar = Calendar.getInstance(TimeZone.getTimeZone("PST"));

Date trialTime = new Date();

calendar.setTime(trialTime);

________

第一部分 JSP 技术与J2EE 技术

}

public int getYear()

{

return calendar.get(Calendar.YEAR);

}

public String getMonth()

{

int m = getMonthInt();

String[] months = new String []

{"January" "February" "March"

"April" "May" "June"

"July" "August" "September"

"October" "November" "December"

};

if (m > 12)

return "Unknown to Man";

return months[m - 1];

}

public String getDay()

{

int x = getDayOfWeek();

String[] days = new String[]

{"Sunday" "Monday" "Tuesday" "Wednesday"

"Thursday" "Friday" "Saturday"

};

if (x > 7)

return "Unknown to Man";

return days[x - 1];

}

public int getMonthInt()

{

return 1 + calendar.get(Calendar.MONTH);

}

public String getDate()

________

第1 章 JavaBeans 组件技术

{

return getMonthInt() + "/" + getDayOfMonth() + "/" + getYear();

}

public String getTime()

{

return getHour() + ":" + getMinute() + ":" + getSecond();

}

public int getDayOfMonth()

{

return calendar.get(Calendar.DAY_OF_MONTH);

}

public int getDayOfYear()

{

return calendar.get(Calendar.DAY_OF_YEAR);

}

public int getWeekOfYear()

{

return calendar.get(Calendar.WEEK_OF_YEAR);

}

public int getWeekOfMonth()

{

return calendar.get(Calendar.WEEK_OF_MONTH);

}

public int getDayOfWeek()

{

return calendar.get(Calendar.DAY_OF_WEEK);

}

public int getHour()

{

return calendar.get(Calendar.HOUR_OF_DAY);

}

public int getMinute()

{

return calendar.get(Calendar.MINUTE);

________

第一部分 JSP 技术与J2EE 技术

}

public int getSecond()

{

return calendar.get(Calendar.SECOND);

}

public int getEra()

{

return calendar.get(Calendar.ERA);

}

public String getUSTimeZone()

{

String[] zones = new String[]

{"Hawaii" "Alaskan" "Pacific"

"Mountain" "Central" "Eastern"

};

int index = 10 + getZoneOffset();

if (index <= 5)

{

return zones[10 + getZoneOffset()];

}

else

{

return "Only US Time Zones supported";

}

}

public int getZoneOffset()

{

return calendar.get(Calendar.ZONE_OFFSET)/(60*60*1000);

}

public int getDSTOffset()

{

return calendar.get(Calendar.DST_OFFSET)/(60*60*1000);

}

public int getAMPM()

________

第1 章 JavaBeans 组件技术

{

return calendar.get(Calendar.AM_PM);

}

}

在程序清单1.17(JspCalendar.java)中定义了一个JavaBean JspCalendar JspCalendar