企业级 JavaBean 会话 Bean 开发全解析
1. EJB 基础原理
1.1 主接口的作用
每个 EJB 组件类都包含一个主接口(home interface),它定义了在应用服务器容器内创建、初始化、销毁以及查找 EJB 实例的所有方法。主接口是 EJB 组件类与容器之间的契约,容器负责定义所有 EJB 实例的构造、销毁和查找操作。
1.2 客户端与服务器的交互流程
当客户端应用程序想要创建一个服务器端 Bean 时,它会使用 JNDI(Java Naming and Directory Interface)来定位所需 Bean 的组件主接口。JNDI 是 Java 核心服务器的扩展,允许客户端在目录中查找任何所需的组件。客户端找到所需 EJB 组件的主接口后,调用其中一个 create() 方法,该方法会调用服务器上的 EJB 容器,容器进而创建 EJB 组件并返回对象的远程接口。
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(客户端):::process -->|使用 JNDI 查找| B(主接口):::process
B -->|调用 create() 方法| C(EJB 容器):::process
C -->|创建 EJB 组件| D(EJB 组件):::process
D -->|返回远程接口| A
1.3 EJB 服务器的优势
EJB 应用服务器是复杂的软件,提供了许多服务,可大大提高开发过程的生产力。这些服务器不仅提供基本的连接功能,还提供事务、安全等许多有用的服务。其服务器组件架构的基本目标包括:
- 创建一个在运行时方法可扩展的服务器。
- 部署一个会计软件包,将其放入正在运行的服务器中,并使用该软件包附带的客户端软件访问服务器功能,而无需关闭服务器,更无需重新编译。
2. 会话 Bean 详解
2.1 会话 Bean 的概述
会话 Bean 是大粒度对象,用于实现高级服务,如购物车、订单管理系统和内容管理系统等。会话 Bean 通常不单独使用,而是与实体 Bean 结合使用,通常会与多个实体 Bean 一起执行处理。例如,订单录入会话 Bean 会调用客户、订单和产品实体 Bean 的行为。需要注意的是,会话 Bean 不能使用容器管理的持久性,要么使用实体 Bean,要么自己连接到数据存储。
2.2 有状态会话 Bean
有状态会话 Bean 是专门为单个客户端服务的组件,仅为该客户端维护状态。例如,网站的购物车会话 Bean 需要为每个用户维护一个单独的列表。当在线商店的购物者选择要购买的商品时,这些商品会被添加到用户的个人购物车中,这通过将会选择的商品存储在购物车(会话 Bean 对象的一个成员)的列表中来实现。Sun 为有状态会话 Bean 的实现定义了以下规则:
- Bean 类实例专用于一个客户端。
- 来自同一客户端的每个方法调用都由同一个实例处理。
- 非活动的 Bean 类实例可以被钝化(交换到二级存储设备)。
2.3 无状态会话 Bean
无状态会话 Bean 与有状态 Bean 相反,每个会话共享一个 Bean,不维护状态,因此可以被多个客户端池化使用。例如,一个返回包含客户信息列表的会话 Bean(getCustomers() 业务方法),客户端调用 getCustomers() 方法并传入列表的条件,该方法会将客户列表返回给客户端。企业 Java Beans(EJB)规范为无状态会话 Bean 定义了以下规则:
|规则|描述|
|----|----|
|Bean 类实例可自由交换|可在容器控制的 EJB 对象之间自由交换 Bean 类实例|
|后续客户端调用可由不同实例处理|后续的客户端调用可以由不同的实例处理|
|空闲 Bean 类实例存储在池中|空闲的 Bean 类实例存储在池中|
|Bean 实例在容器关闭前不销毁|Bean 实例在容器关闭之前不会被销毁|
2.4 会话 Bean 的生命周期
2.4.1 从客户端角度看
会话 Bean 的生命周期由客户端与该 Bean 的通信控制。Bean 通常代表客户端将访问的一些非持久组件。其生命周期的初始状态是“不存在且未被引用”,客户端通过调用主对象接口的 create() 方法创建 Bean 实例,此时容器会创建该 Bean。之后,客户端可以调用会话 Bean 上的任何方法,完成后可以使用远程对象的 remove() 方法或主接口的 remove() 方法移除该 Bean。客户端通知 Bean 移除后,应将引用标记为 null,这会将客户端对 Bean 的视图从“存在但未被引用”变为“不存在且未被引用”。
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(不存在且未被引用):::process -->|home.create()| B(存在且被引用):::process
B -->|调用方法| B
B -->|object.remove() 或 home.remove()| C(存在但未被引用):::process
C -->|标记引用为 null| A
B -->|容器崩溃或 Bean 超时| A
2.4.2 从 Bean 角度看
无状态会话 Bean 的状态图很简单,只包含两个状态:存在或不存在。Bean 创建后会进入可用的无状态 Bean 池,客户端从 Bean 池中取出一个 Bean 并执行其上的方法,方法完成后,该 Bean 会返回可用 Bean 池,以供另一个客户端请求使用。
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(不存在):::process -->|newInstance()| B(对象准备 - 池中):::process
B -->|调用方法| B
B -->|ejbRemove()| A
3. 编写客户端
3.1 客户端连接 Bean 的步骤
客户端与 EJB 协作需要执行以下步骤:
1. 获取 JNDI 服务器的引用。
2. 获取所需 Bean 的主对象的引用。
3. 通过调用 create() 方法或查找方法获取远程对象的引用。
4. 调用所需的业务方法。
3.2 示例:天气会话 Bean 客户端
以下是创建一个与天气会话 Bean 通信的客户端的具体步骤和代码:
package sessionbean;
import java.rmi.*;
import javax.ejb.*;
import javax.naming.*;
class WeatherClient {
public static void main(String[] args) {
try {
// 1. 获取 JNDI 上下文
InitialContext ctx = new InitialContext();
// 2. 查找所需对象
Object objRef = ctx.lookup("WeatherStation");
// 3. 转换为正确的接口
WeatherStationHome stationHome = (WeatherStationHome) javax.rmi.PortableRemoteObject.narrow(objRef, WeatherStationHome.class);
// 4. 请求容器提供对象
WeatherStation station = stationHome.create();
// 5. 调用所需方法
System.out.println("Current Temperature is:");
System.out.println(station.currentTemp("46304"));
} catch (RemoteException ex) {
System.err.println("Remote Exception:" + ex);
} catch (CreateException ex) {
System.err.println("Create Exception:" + ex);
} catch (NamingException ex) {
System.err.println("Naming Exception:" + ex);
} catch (ClassCastException ex) {
System.err.println("Cast Exception:" + ex);
}
}
}
3.3 客户端与 Bean 通信的详细过程
为了更好地理解客户端与 Bean 通信时的底层细节,我们可以从组件图和序列图两个角度进行分析。
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(客户端 1):::process -->|stub| B(远程组件接口):::process
C(客户端 2):::process -->|stub| B
B -->|请求| D(EJB 容器):::process
D -->|响应| B
B -->|返回结果| A
B -->|返回结果| C
D -->|管理| E(Bean 实例池):::process
序列图展示了使用无状态会话 Bean 的简单场景:
1. 远程客户端在调用 Bean 的业务方法之前,需要使用远程主接口创建一个新的 Bean 实例或从容器管理的池中移除一个实例。
2. 作为响应,容器创建一个新的 EJB 对象,并将远程组件接口的引用传递回客户端使用。
3. 客户端可以使用 Bean 远程接口中定义的业务方法进行调用。
4. EJB 对象接收到请求后,容器会分配一个 Bean 类实例来处理该请求。
5. 如果池中没有可用实例,可能需要创建一个新实例,客户端的调用会转发到该实例。
6. 客户端完成所有必要的调用后,应移除该 Bean。
sequenceDiagram
participant Client
participant EJBHome
participant EJBObject
participant Instance
participant Database
Client->>EJBHome: create()
EJBHome->>Instance: Class.newInstance()
Instance->>EJBObject: 初始化
EJBObject->>Client: 返回远程接口引用
Client->>EJBObject: 调用业务方法
EJBObject->>Instance: 转发请求
Instance->>Database: 数据操作
Database->>Instance: 返回数据
Instance->>EJBObject: 返回结果
EJBObject->>Client: 返回结果
Client->>EJBObject: remove()
EJBObject->>Instance: 销毁实例
3.4 有状态和无状态会话 Bean 的客户端差异
使用有状态会话 Bean 时,客户端代码基本不变,只是使用方式有所不同。例如,不再直接传入 ZIP 代码,而是使用一个修改器(mutator),然后调用方法。
package sessionbean;
import java.rmi.*;
import javax.ejb.*;
import javax.naming.*;
class WeatherClient {
public static void main(String[] args) {
try {
InitialContext ctx = new InitialContext();
Object objRef = ctx.lookup("WeatherStation");
WeatherStationHome stationHome = (WeatherStationHome) javax.rmi.PortableRemoteObject.narrow(objRef, WeatherStationHome.class);
WeatherStation station = stationHome.create();
station.setZipcode("46304");
System.out.println("Current Temperature is:");
System.out.println(station.currentTemp());
} catch (RemoteException ex) {
System.err.println("Remote Exception:" + ex);
} catch (CreateException ex) {
System.err.println("Create Exception:" + ex);
} catch (NamingException ex) {
System.err.println("Naming Exception:" + ex);
} catch (ClassCastException ex) {
System.err.println("Cast Exception:" + ex);
}
}
}
4. 编写会话 Bean
4.1 JBuilder 开发会话 Bean 的优势
JBuilder 为开发 EJB 应用提供了许多便利,有助于快速开发和部署会话 Bean。其集成的生产力特性包括:
-
EJB 设计器
:可图形化构建 JavaBean 之间的相互关系,虽对会话 Bean 的帮助不如实体 Bean 大,但能辅助可视化接口设计。
-
各种向导
:帮助用户进行 EJB 项目的开发、管理和部署,如 EJB 模块向导负责归档和管理 EJB 组件。
-
测试客户端
:开发者可使用测试客户端向导快速创建客户端来测试 EJB 组件及其方法,将测试代码与生产代码分离。
-
多厂商支持
:支持多种应用服务器,如 Borland 企业服务器、IBM 的 WebSphere、BEA WebLogics 和 Sun 的 IPlanet 等,还可通过自定义应用服务器选项添加其他支持。
4.2 创建会话 Bean 的步骤
4.2.1 准备工作
在 JBuilder 中创建会话 Bean 前,需完成以下准备任务:
1.
配置企业服务器目标
:让 JBuilder 识别并与所选的应用服务器环境进行通信,配置相应的功能、代码生成器、向导、命令行实用程序、类路径以及部署和运行时选项。
2.
创建项目
:选择 File -> New Project 创建一个空的 JBuilder 项目,并在项目属性编辑器中添加应用服务器的库。
3.
创建 EJB 模块
:使用 JBuilder 的 EJB 模块向导创建 EJB 模块,需设置以下属性:
|属性|描述|示例|
|----|----|----|
|名称|EJB 模块的逻辑名称,通常与 JAR 文件名称相同|SessionBeanSample|
|格式|指定会话 Bean 的存储格式,XML 更易修改,常用|XML|
|版本|告知 JBuilder 关于 EJB 规范的兼容性,如 EJB 1.x 或 2.0|EJB 2.0 兼容|
|输出 JAR 名称|指定构建 EJB 组件时的 JAR 文件名|SessionBeanSample|
|路径|指定存储 EJB JAR 文件的路径|C:\Project\Sample\SessionBean|
4.2.2 构建会话 Bean
构建会话 Bean(有状态或无状态)的生产任务如下:
1.
创建 1.x 或 2.0 会话 Bean
:使用 JBuilder 的 EJB 设计器创建会话 Bean,具体步骤如下:
- 双击要用作新会话 Bean 容器的 EJB 模块。
- 右键单击设计窗口或节点,选择 New Session Bean。
- 配置会话 Bean 的属性,如 Bean 名称、接口类型(远程)和会话类型(无状态)。
- 右键单击新会话 Bean 并添加方法,如添加 getCurrentTemp 方法,设置返回类型为 Int,输入参数为 java.lang.String zipcode,接口为远程。
- 为生成的代码指定包名和类名,如将默认包名改为 com.sams.weatherstation.session。
2.
实现业务逻辑
:
- 找到实现业务逻辑的 Bean 文件,通常为
Bean.java,如 WeatherStationBean.java。
- 在该文件中找到需要实现业务逻辑的方法,可通过 JBuilder 的 Structure 面板中的 to - do 文件夹快速定位。以下是实现 getCurrentTemp 方法的示例:
package com.sams.weather.session;
import java.util.*;
import javax.ejb.*;
public class WeatherStationBean implements SessionBean {
SessionContext sessionContext;
public void ejbRemove() {
/**@todo Complete this method*/
}
public void ejbActivate() {
/**@todo Complete this method*/
}
public void ejbPassivate() {
/**@todo Complete this method*/
}
public void setSessionContext(SessionContext sessionContext) {
this.sessionContext = sessionContext;
}
/**
* Get the current temperature by randomly generating one
* @param zipcode
* @return current temp
*/
public int getCurrentTemp(java.lang.String zipcode) {
int currentTemp;
Random rnd = new Random(System.currentTimeMillis());
currentTemp = rnd.nextInt(110);
return currentTemp;
}
public void ejbCreate() throws CreateException {
/**@todo Complete this method*/
}
}
- 编译和归档 Bean :使用 JBuilder 的编译和归档功能将 Bean 代码编译成字节码并打包成 JAR 文件。
- 创建/修改部署描述符 :每个 EJB 都需要一个部署描述符,用于向容器描述会话 Bean 的类型和配置。以下是 WeatherStation Bean 的部署描述符示例:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb - jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
"http://java.sun.com/dtd/ejb - jar_2_0.dtd">
<ejb - jar>
<enterprise - beans>
<session>
<display - name>WeatherStation</display - name>
<ejb - name>WeatherStation</ejb - name>
<home>com.sams.weather.session.WeatherStationHome</home>
<remote>com.sams.weather.session.WeatherStation</remote>
<ejb - class>com.sams.weather.session.WeatherStationBean</ejb - class>
<session - type>Stateless</session - type>
<transaction - type>Container</transaction - type>
</session>
</enterprise - beans>
<assembly - descriptor>
<container - transaction>
<method>
<ejb - name>WeatherStation</ejb - name>
<method - name>*</method - name>
</method>
<trans - attribute>Required</trans - attribute>
</container - transaction>
</assembly - descriptor>
</ejb - jar>
JBuilder 会自动创建部署描述符,也可使用编辑器进行修改。
5.
创建测试客户端进行单元测试
:使用 EJB 测试客户端向导创建测试客户端,可设置以下属性:
|属性|描述|示例|
|----|----|----|
|EJB 名称|指定要测试的 EJB|WeatherStation|
|包|指定测试客户端所在的包,避免与生产代码混淆|com.sams.testclient|
|类名|生成的测试类的名称|WeatherStationTestClient|
|基类|通常不使用,除非有测试客户端继承的框架|SamsTestFrame|
|生成测试远程接口调用的方法|添加一个使用默认参数调用所有远程接口调用的方法|getCurrentTemp(“”)|
|生成日志消息|生成显示日志消息的代码,跟踪执行和计时|N/A|
|生成主函数|为客户端添加主函数,以便运行测试客户端|N/A|
以下是生成的测试客户端代码示例:
package com.sams.testclient;
import com.sams.weather.session.*;
import javax.naming.*;
import javax.rmi.PortableRemoteObject;
public class WeatherStationTestClient {
static final private String ERROR_NULL_REMOTE =
"Remote interface reference is null. It must be " +
"created by calling one of the Home interface methods first.";
static final private int MAX_OUTPUT_LINE_LENGTH = 100;
private boolean logging = true;
private WeatherStationHome weatherStationHome = null;
private WeatherStation weatherStation = null;
// Construct the EJB test client
public WeatherStationTestClient() {
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("WeatherStation");
// cast to Home interface
weatherStationHome = (WeatherStationHome)
PortableRemoteObject.narrow(ref, WeatherStationHome.class);
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 WeatherStation create() {
long startTime = 0;
if (logging) {
log("Calling create()");
startTime = System.currentTimeMillis();
}
try {
weatherStation = weatherStationHome.create();
if (logging) {
long endTime = System.currentTimeMillis();
log("Succeeded: create()");
log("Execution time: " + (endTime - startTime) + " ms.");
}
} catch (Exception e) {
if (logging) {
log("Failed: create()");
}
e.printStackTrace();
}
if (logging) {
log("Return value from create(): " + weatherStation + ".");
}
return weatherStation;
}
//--------------------------------------------------------------------------
// Methods that use Remote interface methods to access data through the bean
//--------------------------------------------------------------------------
public int getCurrentTemp(String zipcode) {
int returnValue = 0;
if (weatherStation == null) {
System.out.println("Error in getCurrentTemp(): " + ERROR_NULL_REMOTE);
return returnValue;
}
long startTime = 0;
if (logging) {
log("Calling getCurrentTemp(" + zipcode + ")");
startTime = System.currentTimeMillis();
}
try {
returnValue = weatherStation.getCurrentTemp(zipcode);
if (logging) {
long endTime = System.currentTimeMillis();
log("Succeeded: getCurrentTemp(" + zipcode + ")");
log("Execution time: " + (endTime - startTime) + " ms.");
}
} catch (Exception e) {
if (logging) {
log("Failed: getCurrentTemp(" + zipcode + ")");
}
e.printStackTrace();
}
if (logging) {
log("Return value from getCurrentTemp(" + zipcode + "): " +
returnValue + ".");
}
return returnValue;
}
public void testRemoteCallsWithDefaultArguments() {
if (weatherStation == null) {
System.out.println("Error in testRemoteCallsWithDefaultArguments(): " +
ERROR_NULL_REMOTE);
return;
}
getCurrentTemp("");
}
//---------------------------------------------------------------------
// Utility Methods
//---------------------------------------------------------------------
private void log(String message) {
if (message == null) {
System.out.println("-- null");
return;
}
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) {
WeatherStationTestClient client = new WeatherStationTestClient();
// 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.
/* Added code to test the interface */
client.create();
System.out.println(client.getCurrentTemp("46304"));
}
}
- 测试企业 JavaBean :运行测试客户端,调用所需的方法,并使用正确和错误的参数数据进行测试,以确保 Bean 正常工作。测试结果示例如下:
-- Initializing bean access.
-- Succeeded initializing bean access.
-- Execution time: 3124 ms.
-- Calling create()
-- Succeeded: create()
-- Execution time: 40 ms.
-- Return value from create(): Stub[repository_id=RMI:com.sams.weather.session.WeatherStation:000000000 ...
-- Calling getCurrentTemp(46304)
-- Succeeded: getCurrentTemp(46304)
-- Execution time: 10 ms.
-- Return value from getCurrentTemp(46304): 16.
- 实现客户端 :根据实际需求,在生产环境中实现客户端代码,调用会话 Bean 的业务方法。
4.3 部署会话 Bean
会话 Bean 创建后,需部署到应用服务器上,供客户端访问。部署过程如下:
1.
创建 EAR 节点
:选择 File -> New,选择 Enterprise 选项卡,选择 EAR 节点添加到项目中,指定 EAR 文件和节点名称。创建后会生成一个 EAR 文件和一个 application.xml 描述符。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN"
"http://java.sun.com/dtd/application_1_3.dtd">
<application>
<display - name>SessionBean</display - name>
<module>
<ejb>SessionBeanSample.jar</ejb>
</module>
</application>
- 部署到应用服务器 :右键单击 SessionBean.eargrp 节点,选择部署 Bean,确保应用服务器正在运行。如果服务器未运行,启动服务器后再次部署。
通过以上步骤,我们可以全面了解企业级 JavaBean 会话 Bean 的开发、测试和部署过程,利用 JBuilder 等工具提高开发效率,确保会话 Bean 在生产环境中稳定运行。
企业级 JavaBean 会话 Bean 开发解析
超级会员免费看
985

被折叠的 条评论
为什么被折叠?



