|
4.3 无状态会话EJB实例
本节演示如何开发和测试一个无状态会话EJB。本节提供的实例演示了一个简化的股票交易操作,示意图如图4-4所示。 从上图看出,无状态EJB:TraderBean由多个客户端共享使用,不保存客户端的状态。 通过这个实例,读者可以知道: ·如何定义无状态EJB的主接口、远程接口和如何编写EJB实现类 ·如何使用对象作为远程方法的返回值 ·如何在ejb-jar.xml中定义和在无状态EJB中通过会话上下文使用环境参数 本例中包括服务器端程序和客户端程序,把服务器端程序放在C:\bea\wlserver6.0\config\mydomain\applications\DefaultWebApp_myserver\WEB-INF\classes目录下,如果没有classes这个目录,请手工创建;把客户端程序放在一个目录下,这里用C:\bea\wlserver6.0\config\mydomain\clientclasses。如果没有子目录clientclasses,则创建它,如果不创建这两个目录,本实例将不能正常运行。
4.3.2 编写源文件
这个例子的代码包括两个接口、三个类。我们把它们都定义在包examples.ejb.basic.statelessSession中,因此创建了目录: C:\work\examples\ejb\basic\statelessSession 该实例使用到的文件列表和描述如表4-1所示。 表4-1
无状态会话EJB文件列表 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 名称 类名 文件名 所在包
───────────────────────────────── 主接口 TraderHome TraderHome.java examples.ejb.basic.statelessSession 远程接口 Trader Trader.java examples.ejb.basic.statelessSession 远程方法返回类 TraderResult TraderResult.java examples.ejb.basic.statelessSession bean实现类 TraderBean.java examples.ejb.basic.statelessSession 客户端测试类 Client Client.java examples.ejb.basic.statelessSession bean说明文件 ejb-jar.xml bean部署文件 weblogic-ejb-jar.xml
───────────────────────────────── 1.主接口程序 无状态Session
Bean的主接口定义比较简单。它本身不需维持自身的状态,所以不需要个性化的创建方法,即create方法是不带有参数的。 无状态Session
Bean的主接口定义比较简单。它本身不需维持自身的状态,所以不需要个性化的创建方法,即create方法是不带有参数的。 编辑TraderHome.java并保存到C:\work\src\examples\ejb\basic\statelessSession目录下(或从附带光盘的src\examples\ejb\basic\statelessSession目录拷贝),其源文件如下:
//定义本接口在包examples.ejb.basic.statelessSession中 package
examples.ejb.basic.statelessSession; //本接口用到的其他类 //javax.ejb.EJBHome定义了EJB主接口,被用户定义的主接口继承 import
java.rmi.RemoteException; import
javax.ejb.CreateException; import javax.ejb.EJBHome;
/** *
这是TradeBean的主接口定义,这个接口是被EJB容器产生的类TraderBeanC实现的。 *
在这里只需定义EJB创建的方法,这些方法要和EJBean中的"ejbCreate"方法对应。 */ //EJBean主接口必须继承javax.ejb.EJBHome接口 public
interface TraderHome extends EJBHome {
/** *
这个方法和"TraderBean.java"中定义的的Bean的"ejbCreate"方法相对应 *
这两个方法的参数应该相同。当客户端调用"TraderHome.create()"方法时,EJB容器 *
会找到EJBean的实例,并调用它的"ejbCreate()"方法。 * * @返回
远程对象Trader * @异常 RemoteException 当系统通讯发生故障时抛出 * @异常
CreateException 创建EJBean错误时抛出 * @参看
examples.ejb.basic.statelessSession.TraderBean */ Trader
create() throws CreateException, RemoteException; }
2.
远程接口程序 在远程接口中定义业务逻辑方法,这个接口中定义了两个业务逻辑方法,buy和sell,分别对应股票交易中的买进和卖出。 编辑文件Trader.java并保存到C:\work\src\examples\ejb\basic\statelessSession目录下(或从附带光盘的src\examples\ejb\basic\statelessSession目录拷贝),其源文件如下:
//文件名:Trader.java //定义本接口在包examples.ejb.basic.statelessSession中 package
examples.ejb.basic.statelessSession; //本接口用到的其他类 //javax.ejb.*中定义了实现EJBean的接口。 import
java.rmi.RemoteException; import javax.ejb.EJBObject;
/** *
这是TradeBean的远程接口定义。远程接口中定义了客户端能远程调用EJBean的方法。这些方法除了 *
要抛出异常java.rmi.RemoteException之外,和EJBean中的定义是一致的。但并不是EJBean来实 *
现这个接口,而是由容器自动产生的类TraderBeanE实现的。 */ //这个接口必须继承javax.ejb.EJBObject接口 public
interface Trader extends EJBObject {
/** * 远程方法:购买指定股票类别、数量的股票。 * * @参数
stockSymbol 股票代码 * @参数 shares 购买股票的数量 * @返回 TradeResult
交易结果对象 * @异常 RemoteException 当系统通讯发生故障时抛出 */ public
TradeResult buy (String stockSymbol, int shares) throws
RemoteException;
/** * 远程方法:出售指定股票类别、数量的股票。 * * @参数
stockSymbol 股票代码 * @参数 shares 出售股票的数量 * @返回 TradeResult
Trade Result * @异常 RemoteException
当系统通讯发生故障时抛出 */ public TradeResult sell (String
stockSymbol, int shares) throws RemoteException; }
3.
远程方法返回结果类 这个例子演示了在EJB远程调用方法返回中如何使用对象。在远程方法sell和buy定义中我们盾到返回值是一个TradeResult对象。对于这样的类定义,需要注意的是它必须实现java.io.Serializable接口。这是因为远程方法返回对象要在不同主机(或虚拟机)之间的网络流之间传递,而在流之间传递的对象必须要实现Serializable接口,否则会抛出异常。 编辑文件TradeResult.java并保存到C:\work\src\examples\ejb\basic\statelessSession目录下(或从附带光盘的src\examples\ejb\basic\statelessSession目录下拷贝),其源文件如下:
//定义本类在包examples.ejb.basic.statelessSession package
examples.ejb.basic.statelessSession;
import java.io.Serializable;
/** *
这个类代表买/卖股票的结果 */ //远程方法使用的类必须实现Serializable接口 public
final class TradeResult implements Serializable {
// 真实的买卖数量 private final int numberTraded;
//股票代号 private final String
stockSymbol; //构造交易结果对象 public TradeResult(int nt, String
ss) { numberTraded = nt; stockSymbol =
ss; } //get方法 public int getNumberTraded() { return
numberTraded; } public String getStockSymbol() { return
stockSymbol; } }
4.
Bean实现类 Bean类用来实现在远程接口中定义的业务逻辑方法,这里简单实现了buy和sell方法。 这个例子演了EJB如何猎取环境参数的方法。作为无状态会话Bean来说,虽然不能在客户端创建的时候指定参数,但可以通过在部署描述符中定义环境参数来给无状态Bean赋值。在实现时可以用下下文InitialContext的lookup()方法猎取参数值。 同时在这个例子中也演示了怎样使用用户定义的异常。 编辑文件TraderBean.java并保存到C:\work\src\examples\ejb\basic\statelessSession目录下(或从附带光盘的src\examples\ejb\basic\statelessSession目录拷贝),其源文件如下:
//定义本类在包examples.ejb.basic.statelessSession中 package
examples.ejb.basic.statelessSession; //本类用到的其他类。javax.ejb.*是开发EJB应用需要的类库。javax.naming.*是实现JNDI服务需要的类库。 import
javax.ejb.CreateException; import
javax.ejb.SessionBean; import
javax.ejb.SessionContext; import
javax.naming.InitialContext; import
javax.naming.NamingException;
/** * TraderBean 是一个无状态EJB,它演示了如何: *
在会话过程中不保持用户的状态 * 用户定义异常 */ //这个类是会话Bean,因此必须实现接口
SessionBean public class TraderBean implements SessionBean
{ //设置是否打印控制台 private static final boolean VERBOSE =
true; //声明会话上下文变量 private SessionContext
ctx; //设计交易限制变量 private int tradeLimit;
// 使用日志记录 private void log(String s) { if
(VERBOSE) System.out.println(s); }
/** *
这是本类必须实现的方法,在本例中没有用到 * */ public void ejbActivate()
{ log("ejbActivate called"); }
/** * 这是本类必须实现的方法,在本例中没有用到 * */ public void
ejbRemove() { log("ejbRemove called"); }
/** * 这是本类必须实现的方法,在本例中没有用到 * */ public void
ejbPassivate() { log("ejbPassivate called"); }
/** * 设置会话上下文变量 * * @参数 ctx SessionContext
Context for session */ public void
setSessionContext(SessionContext ctx) { log("setSessionContext
called"); this.ctx = ctx; }
/** * 这个方法与"TraderHome.java"中定义的主接口中的"create"方法相对应 *
两个方法的参数相同。当客户端调用主接口的"TraderHome.create()"方法时, *
容器会分配一个EJBean实例,并调用它的"ejbCreate()"方法。 * * @异常
javax.ejb.CreateException 创建EJBean出错时抛出的异常 * @参看
examples.ejb.basic.statefulSession.Trader */ public void
ejbCreate () throws CreateException { log("ejbCreate
called");
try { //初始化JNDI名称服务上下文 InitialContext ic = new
InitialContext(); //从环境变量上下文中查找交易限制参数值 Integer tl =
(Integer)
ic.lookup("java:/comp/env/tradeLimit");
tradeLimit =
tl.intValue(); } catch (NamingException ne) { throw new
CreateException("Failed to find environment value
"+ne); } }
/** * 购买指定用户和股票类别、数量的股票。 * * @参数 stockSymbol
股票代码 * @参数 shares 购买股票的数量 * @返回 TradeResult
交易结果对象 */ public TradeResult buy(String stockSymbol, int
shares) {
if (shares > tradeLimit) { log("Attempt to buy
"+shares+" is greater than limit of "+tradeLimit); shares =
tradeLimit; }
log("Buying "+shares+" shares of
"+stockSymbol); //返回交易结果对象 return new TradeResult(shares,
stockSymbol); }
/** * 出售指定用户和股票类别、数量的股票。 * * @参数 stockSymbol
股票代码 * @参数 shares 出售股票的数量 * @返回 TradeResult Trade
Result */ public TradeResult sell(String stockSymbol, int
shares) {
if (shares > tradeLimit) { log("Attempt to sell
"+shares+" is greater than limit of "+tradeLimit); shares =
tradeLimit; }
log("Selling "+shares+" shares of
"+stockSymbol); //返回交易结果对象 return new TradeResult(shares,
stockSymbol); } }
5.
编辑部署文件 会话EJB使用两个部署说明文件,ejb-jar.xml用来描述EJB的结构、类型、环境参数等。weblogic-ejb-jar.xml用来描述与部署相关的信息。 编辑ejb-jar.xml和weblogic-ejb-jar.xml并保存到C:\work\src\examples\ejb\basic\statelessSession目录下(或从附带光盘的src\examples\ejb\basic\statelessSession目录拷贝),其源文件如下:
<?xml version="1.0"?> <!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> <small-icon>images/green-cube.gif</small-icon> <enterprise-beans> <session> <small-icon>images/orange-cube.gif</small-icon> <ejb-name>statelessSession</ejb-name> <home>examples.ejb.basic.statelessSession.TraderHome</home> <remote>examples.ejb.basic.statelessSession.Trader</remote> <ejb-class>examples.ejb.basic.statelessSession.TraderBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> <env-entry> <env-entry-name>WEBL</env-entry-name> <env-entry-type>java.lang.Double
</env-entry-type> <env-entry-value>10.0</env-entry-value> </env-entry> <env-entry> <env-entry-name>INTL</env-entry-name> <env-entry-type>java.lang.Double
</env-entry-type> <env-entry-value>15.0</env-entry-value> </env-entry> <env-entry> <env-entry-name>tradeLimit</env-entry-name> <env-entry-type>java.lang.Integer
</env-entry-type> <env-entry-value>500</env-entry-value> </env-entry> </session> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>statelessSession</ejb-name> <method-intf>Remote</method-intf> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
ejb-jar.xml可以分为以下几个部分: <ejb-name>:定义这个EJB的名字,这里命名为statelessSession。 <home>:指定主接口,这里指定为examples.ejb.basic.statelessSession.TraderHome。 <remote>:指定远程接口,这里指定为examples.ejb.basic.statelessSession.Trader。 <ejb-class>:指定EJB类,这里指定为examples.ejb.basic.statelessSession.TraderBean。 <session-type>:定义会话类型,这里定义为Stateless,无状态。 <env-entry>:定义环境参数名和值,可以通过EJB类中上下文对象的lookup方法获取。 weblogic-ejb-jar.xml的源文件如下:
<?xml version="1.0"?>
<!DOCTYPE weblogic-ejb-jar PUBLIC '-//BEA Systems, Inc.//DTD
WebLogic 5.1.0 EJB//EN'
'http://www.bea.com/servers/wls510/dtd/weblogic-ejb-jar.dtd'>
<weblogic-ejb-jar> <weblogic-enterprise-bean> <ejb-name>statelessSession</ejb-name> <caching-descriptor> <max-beans-in-free-pool>100</max-beans-in-free-pool> </caching-descriptor> <jndi-name>statelessSession.TraderHome</jndi-name> </weblogic-enterprise-bean> </weblogic-ejb-jar>
weblogic-ejb-jar.xml文件主要定义了与EJB部署相关的信息,主要有: <ejb-name>:EJB的名字,这里是statelessSession <caching-descriptor>:EJB缓冲池描述,这里定义了自由池的EJB实例数。 注意
Weblogic
Server中,为了提高无状态EJB的访问效率,采用了自由池的技术,所谓自由池技术就是在自由池中为同一个无状态EJB类实例化多个对象(指定最大数),来处理多用户折情况,提高效率,而在客户端看来,却是透明的,和访问同一个实例进行的处理方法一样。 6.
客户端测试程序代码编写 编辑client.java文件并保存到C:\work\src\examples\ejb\basic\statelessSession目录下(也可以直接从配套光盘的类似路径下拷贝),其源文件的代码如下:
//定义本类在包examples.ejb.basic.statelessSession中 package
examples.ejb.basic.statelessSession; //本类用到的其他类 import
java.rmi.RemoteException; import java.util.Properties;
import javax.ejb.CreateException; import
javax.ejb.RemoveException; import
javax.naming.Context; import
javax.naming.InitialContext; import
javax.naming.NamingException; import
javax.rmi.PortableRemoteObject;
/** * 这个类演示了如何调用一个会话,并进行如下操作: * 创建一个Trader远程对象 *
使用Trader远程对象购买一定数量的股票 * 出售一定数量的股票 *
清除Trader远程对象 */
public class Client { //定义JNDI服务中EJB的名称 private
static final String JNDI_NAME =
"statelessSession.TraderHome"; //声明应用服务器的URL变量 private
String url; //声明主接口变量 private TraderHome home;
public Client(String url) throws
NamingException {
this.url = url; //寻找主接口 home =
lookupHome(); }
/** * 在命令行运行这个实例: * java
examples.ejb.basic.statefulSession.Client
"t3://localhost:7001" * 参数是可选的 */ public static void
main(String[] args) throws Exception {
log("\nBeginning
statelessSession.Client...\n"); //声明应用服务器地址变量 String url =
"t3://localhost:7001"; // Parse the argument list if
(args.length != 1) { //打印用法提示 System.out.println("Usage:
java examples.ejb.basic.statelessSession.Client
t3://hostname:port"); return; } else { url =
args[0]; }
Client client = null; try { //创建客户端实例 client =
new Client(url); } catch (NamingException ne)
{ System.exit(1); }
try { //调用测试方法 client.example(); } catch
(Exception e) { //异常处理 log("There was an exception while
creating and using the Trader."); log("This indicates that
there was a problem communicating with the server: "+e); }
log("\nEnd statelessSession.Client...\n"); }
/** * 运行实例 */ public void example() throws
CreateException, RemoteException, RemoveException {
//
创建远程对象 log("Creating a trader"); Trader trader = (Trader)
narrow(home.create(), Trader.class);
String [] stocks = {"BEAS", "MSFT", "AMZN", "HWP" };
// 执行一系列购买操作 for (int i=0; i<stocks.length; i++)
{ int shares = (i+1) * 100; log("Buying "+shares+" shares
of "+stocks[i]+"."); trader.buy(stocks[i], shares); }
// 执行一系列出售操作 for (int i=0; i<stocks.length; i++)
{ int shares = (i+1) * 100; log("Selling "+shares+" shares
of "+stocks[i]+"."); trader.sell(stocks[i], shares); }
// 清除远程对象 log("Removing the
trader"); trader.remove();
}
/** * 使用RMI/IIOP的narrow方法 */ private Object
narrow(Object ref, Class c) { return
PortableRemoteObject.narrow(ref, c); }
/** * 在JNDI树中寻找EJB主接口 */ private TraderHome
lookupHome() throws NamingException { //
使用JNDI寻找EJB Context ctx = getInitialContext();
try
{ Object home = ctx.lookup(JNDI_NAME); return (TraderHome)
narrow(home, TraderHome.class); } catch (NamingException ne)
{ //异常处理 log("The client was unable to lookup the EJBHome.
Please make sure "); log("that you have deployed the ejb with
the JNDI name "+JNDI_NAME+" on the WebLogic server at
"+url); throw ne; } }
/** * 使用属性对象获取JNDI服务上下文 */ private Context
getInitialContext() throws NamingException {
try { //
声明属性对象 Properties h = new
Properties(); h.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); h.put(Context.PROVIDER_URL,
url); //返回的特定属性的上下文对象 return new InitialContext(h); }
catch (NamingException ne) { //异常处理 log("We were unable to
get a connection to the WebLogic server at "+url); log("Please
make sure that the server is running."); throw
ne; } }
/** * 可以使用jndi.properties属性文件 * */ //
private static Context getInitialContext() // throws
NamingException // { // return new
InitialContext(); // } //打印控制台输出 private static void
log(String s) { System.out.println(s); } }
4.3.3 编译与打包
1.
编译代码 ·打开命令窗口,进入c:\work\src\examples\ebj\basic\statelessSession目录。 ·运行设置环境变量命令脚本setEnv.cmd: c:\work\src\examples\ejb\basic\statelessSession>c:\work\setEnv setEnv.cmd在附带光盘的work目录下,可以直接把它拷贝到c:\work\目录。
·编译好的代码要放在build目录下,因此在编译之前要创建build目录: c:\work\src\examples\ebj\basic\statelessSession、>md
build
·打开命令行窗口,进行c:\work\src\examples\ebj\basic\statelessSession目录,运行: c:\work\src\examples\ebj\basic\statelessSession>javac
-d build Trader.java TraderHome.java TradeResult.java
TraderBean.java 其中 -d build
表示编译后的字节码文件放在build子目录下。 这个命令执行的结果是在build目录下生成examples目录。 2.
打包 打包之前,拷贝必要的文件到规定的目录。继续使用上面的步骤及命令,运行如下命令: c:\work\src\examples\ejb\basic\statelessSession>mkdir
build c:\work\src\examples\ejb\basic\statelessSession>mkdir
build\META-INF c:\work\src\examples\ejb\basic\statelessSession>mkdir
build\images c:\work\src\examples\ejb\basic\statelessSession>copy
*.xml build\META-INF
c:\work\src\examples\ejb\basic\statelessSession>copy *.gif
build\images
用jar命令打包,包括三部分文件,分别在build目录的三个子目录下: ·META-INF:xml部署文件 ·examples:类文件 ·images:图像文件 ·在命令窗口中继续运行如下命令: c:\work\src\examples\ejb\basic\statelessSession>cd
build c:\work\src\examples\ejb\basic\statelessSession>jar
cv0f std_ejb_basic_statelessSession.jar META-INF examples
images 这个命令执行的结果是在build目录下生成std_ejb_basic_statelessSession.jar文件。 3.编译容器代码 这是与应用程序服务器平台相关的一个操作。WebLogic
Server
6.0自带了一个容器代码编译工具,weblogic.ejbc,这是个java程序,并包含在weblogic.jar包中。 在命令窗口中继续运行如下命令: c:\work\src\examples\ejb\basic\statelessSession>java
weblogic.ejbc -compiler javac
build\std_ejb_basic_statelessSession.jar
%WL_HOME%\config\%DOMAIN_NAME%\applications\ejb_basic_statelessSession.jar 该命令生成的代码直接放到了该EJB部署的目录。 4.
编译客户端代码 把客户端程序入在一个目录下,例如c:\bea\wlserver6.0\config\mydomain\clientclasses。如果目录C:\bea\wlserver6.0\config\mydomain下没有子目录clientclasses则创建它。接着在命令窗口中,操作如下: ·在命令窗口中继续运行如下命令: c:\work\src\examples\ejb\basic\statelessSession>javac
-d %EX_WEBAPP_CLASSES% Trader.java TraderHome.java
TradeResult.java c:\work\src\examples\ejb\basic\statelessSession>javac
-d %CLIENT_CLASSES% Trader.java TraderHome.java TradeResult.java
Client.java 5.
脚本命令文件build.cmd 上面在EJB的开发过程中使用命令行的方式,我们可以把所有这些命令行集中起来做成一个命令脚本文件,用这个命令脚本文件,可以一次性执行这些命令,而不必一个命令一个命令的执行。该文件名为build.cmd,放在C:\work\examples\ejb\basic\statelessSession目录(也可以从光盘的\work\examples\ejb\basic\statelessSession目录中拷贝),其内容为:
@REM 创建 build 目录,拷贝部署描述符文件
mkdir build mkdir
build\META-INF mkdir build\images copy *.xml
build\META-INF copy *.gif build\images
@REM 编译 EJB 类文件 javac -d build Trader.java TraderHome.java
TradeResult.java TraderBean.java
@REM 打包成 jar 文件,包括 XML 形式的部署文件 cd build jar cv0f
std_ejb_basic_statelessSession.jar META-INF examples images cd
..
@REM 运行 EJBC java weblogic.ejbc -compiler javac
build\std_ejb_basic_statelessSession.jar
%WL_HOME%\config\%DOMAIN_NAME%\applications\ejb_basic_statelessSession.jar
@REM 确保 Servlets 和 Jsp能访问这个 EJB javac -d
%EX_WEBAPP_CLASSES% Trader.java TraderHome.java TradeResult.java
@REM 编译客户端程序,确保客户端程序能访问这个 EJB javac -d %CLIENT_CLASSES%
Trader.java TraderHome.java TradeResult.java Client.java
这样,在程序代码文件编辑好的情况下,只运行这个命令脚本程序,就可完成EJB开发。 注意
运行build.cmd之前,仍然要运行设置环境变量脚本命令servEnv.cmd: C:\work\src\examples\ejb\basic\statelessSession>c:\work\setEnv.cmd
4.3.4 运行测试
首先,启动WebLogic Server 6.0服务器。WebLogic Server的启动是个简单的过程,可以通过开始菜单和直接运行启动命令脚本两种方式,详细情况请参见第1章。在这里用第二种方法,直接运行C:\bea\wlserver6.0\config\mydomain目录下的startWebLogic.cmd程序。
运行测试程序。继续使用前几个步骤使用的命令窗口,执行:
C:\work\src\examples\ejb\basic\statelessSession>java examples.ejb.basic.statelessSession.Client
t3://127.0.0.1:7001/
观察命令窗口的运行情况,如图4-5所示。
同时,在WebLogic服务器端的命令窗口,显示如图4-6所示。
另外,对于已经部署到WebLogic Server中的EJB,我们可以通过管理控制台程序,来查看。在浏览器的地址栏输入http://127.0.0.1:7001/console/domain/index.jsp。回车。在控制台程序左面板进行选择Deploymens/EJB,则在面板右侧显示已经部署到服务器所有EJB。
其中显示已部署的EJB有两个,分别是本节的EJB,ejb_basic_statelessSession,和第3章的myfirstejb_hello。
|