实战ejb系列
在以后的日子里,将由jackliu向大家陆续提供一系列ejb教程,有学习ejb的朋友请同步参考ejb相关书籍,实战系列将以例程的方式帮助你理解这些基本的概念,其中将包括:
点击查看大图
所有章节完毕后将制作成pdf电子文档,供大家下载。
实战ejb之二 开发会话bean(无状态会话bean)
会话bean可以分为有状态会话bean(stateful bean)和无状态会话bean(stateless bean),有状态会话bean可以在客户访问之间保存数据,而无状态会话bean不会在客户访问之间保存数据。两者都实现了javax.ejb.sessionbean接口,ejb容器区通过部署文件ejb-jar.xml来判断是否为一个sessionbean提供保存状态的服务,另外,在程序实现上,无状态bean不能声明实例变量,每个方法只能操作方法传来的参数。实际上,我们第一节中的第一个ejb程序就是一个无状态session
bean。
在本节中你将了解到:
- 什么是无状态session bean?
- 无状态session bean寿命周期
- 编写一个无状态session bean程序
- 部署到应用服务器
- 开发和部署测试程序
- 运行测试程序
什么是无状态session bean?
无状态session bean每次调用只对客户提供业务逻辑,但不保存客户端的任何数据状态。但并不意味着stateless类型的bean没有状态,而是这些状态被保持在客户端,容器不负责管理。如《再别康桥》中写到的"悄悄的我走了,正如我悄悄的来。挥一挥衣袖,不带走一片云彩"。
无状态session bean在ejb中是最简单的一种bean,如果数据实际上是数据的瞬像,则建议使用无状态会话bean。但是无状态会话bean也有自己的问题,本该存储在服务器端(j2ee服务器)的数据被存储在客户中,每次调用这些数据都要以参数的方式传递给bean,如果是一个比较复杂的数据集合,则网络需要传递大量数据,造成更多的负载。在客户端维护状态还要注意安全性问题,如果数据状态非常敏感,则不要使用无状态会话bean,这些情况可以使用状态会话bean,将用户状态保存到服务器中。
无状态session bean寿命周期
无状态session bean寿命周期由容器控制,bean的客户并不实际拥有bean的直接引用,当我们部署一个ejb时,容器会为这个bean分配几个实例到组件池(component
pooling)中,当客户请求一个bean时,j2ee服务器将一个预先被实例化的bean分配出去,在客户的一次会话里,可以只引用一次bean,就可以执行这个bean的多个方法。如果又有客户请求同样一个bean,容器检查池中空闲的bean(不在方法中或事务中,如果一个客户长时间引用一个bean但执行一个方法后需要等待一段时间再执行另一个方法,则这段时间也是空闲的),如果全部的实例都已用完则会自动生成一个新的实例放到池中,并分配给请求者。当负载减少时,池会自动管理bean实例的数量,将多余的实例从池中释放。
无状态session bean有两种状态:存在或不存在。
<图2-1>
当客户端不存在一个无状态session bean时,通过远程主接口的create()方法创建一个bean,newinstance()负责将bean实例化,ejb容器调用bean类的setsessioncontext()方法把运行环境对象sessioncontext传递给bean;随后调用bean的ejbcreate方法进行必要的初始化和资源分配。在下面这个实战例子中,bean的实现类就是statelessdateejb类。
编写一个无状态的session bean程序
这个session bean组件提供一个日期计算器,通过getdayinrange()方法计算两个日期之间相差的天数,通过getdayforolympic()得到距离北京申办2008年奥林匹克运动会天数。并且我们为这个bean起名为statelessdate
设计一个无状态的session bean至少包括四个步骤:
- 开发主接口
- 开发组件接口
- 开发bean实现类
- 编写部署文件
注意:本节假设你使用的windows操作系统。如果使用其他操作系统,可能影响到存储路径和jdk命令,但这与程序代码和部署文件内容无关。
1.开发主接口(statelessdatehome.java):
是由bean开发人员编写的一个bean的主接口(interface)程序,负责控制一个bean的生命周期(生成、删除、查找bean)。只需要开发人员给出一个主接口类,类方法的实现由容器来完成。
主接口扩展了javax.ejb.ejbhome接口,参考avax.ejb.ejbhome接口定义如下:
package javax.ejb; import java.rmi.remote; import java.rmi.remoteexception; public interface ejbhome extends remote{ public abstract ejbmetadata getejbmetadata() throws remoteexception; public abstract homehandle gethomehandle() throws remoteexception; public abstract void remove(object obj) throws remoteexception,removeexception; public abstract void remove(handle handle) throws remoteexception,removeexception; }
|
- 方法getejbmetadata()返回ejbmetadata接口的引用,取得bean的信息,ejbmetadata不是远程接口。这个类扩展了java.io.serializable,所以可序列化,具有序列化的特性
- 方法gethomehandle()返回主对象的句柄,句柄是主接口statelessdatehome的持久性引用,这个类扩展了java.io.serializable,所以可序列化,具有序列化的特性,homehandle
对象可以传递给另一个jvm,且不传递安全信息,这样新的应用可以不使用jndi来查找对象既可以获得这个主接口,并来创建和获得bean实例。
- 方法remove()用来删除一个bean的实例,对于一个会话bean,执行remove操作将引用的bean返回到池中,由池来管理其生命周期。
一般情况下,习惯将主接口的命名规则规定为<bean-name>home,所以我们把这个主接口类起名为statelessdatehome
大部分逻辑方法已经被ejbhome定义,在我们要设计的远程主接口statelessdatehome里,不必再重新定义。值得注意的是,我们需要为这个接口定义一个create()方法,用来获得一个实例bean的引用,返回的对象类型是组件接口类statelessdate。
statelessdatehome.java代码:
import java.rmi.remoteexception; import javax.ejb.createexception; import javax.ejb.ejbhome;
public interface statelessdatehome extends ejbhome{ public statelessdate create() throws remoteexception,createexception; }
|
假设我们保存到d:ejbstatelessdatesrcstatelessdatehome
.java
2.开发组件接口(statelessdate.java):
当远程用户调用主接口类生成方法(create())时,客户要得到一个组件的远程引用,因此ejb容器要求你为这个bean的所有方法提供一个接口类,而类的实现则与远程主接口statelessdatehome
一样由容器在部署时自动生成。
组件接口扩展了avax.ejb.ejbobject接口,参考avax.ejb.ejbobject接口定义如下:
package javax.ejb; import java.rmi.remote; import java.rmi.remoteexception; public interface ejbobject extends remote{ public abstract ejbhome getejbhome() throws remoteexception; public abstract handle gethandle() throws remoteexception; public abstract object getprimarykey() throws remoteexception; public abstract boolean isidentical(ejbobject ejbobject) throws remoteexception; public abstract void remove() throws remoteexception,removeexception; }
|
- 方法getejbhome()返回远程主接口对象的引用
- 方法gethandle() 当前组件接口对象的句柄,和远程主接口的句柄homehandle一样,这个对象是被序列化的,所以可以保存到本地或通过rmi/iiop协议传输给其他jvm上的客户使用,而免去jndi查找和调用主接口的create方法,只要执行handle.getejbobject()方法即可取得这个bean实例的引用。
- getprimarykey()方法一般用于entity bean,如果在session bean中调用,抛出java.rmi.remoteexception。
- 方法isidentical()用于对当前引用的bean实例和另一bean实例进行比较,因为即便是bean实例相同但有可能不是来自同一个引用,不能使用equals()方法。
- 方法remove() 删除当前引用的bean实例,由容器来决定是否真的释放气内存,通常会返换到组件池中。注意删除之后要将对象的引用指向为null。
一般情况下,习惯将组件接口的命名规则规定为<bean-name>,所以我们把这个组件接口类起名为statelessdate
大部分逻辑方法已经被ejbobject 定义,在我们要设计的组件接口statelessdate里,不必再重新定义,只要我们重申组件中有关业务逻辑的接口即可。逻辑方法getdayinrange()得到两个日期间的天数间隔,如果输入的时间非法或不合适将抛出insufficientdateexception异常。逻辑方法getdayforolympic()得到距离北京申办奥运会的天数,如果输入的时间非法或不合适将抛出insufficientdateexception异常。
statelessdate.java代码:
import javax.ejb.ejbobject; import java.rmi.remoteexception; import java.util.date;
public interface statelessdate extends ejbobject{ public int getdayinrange(date lowerlimitdate,date upperlimitdate) throws remoteexception,insufficientdateexception; public int getdayforolympic() throws remoteexception,insufficientdateexception; }
|
假设我们保存到d:ejbstatelessdatesrcstatelessdate
.java
insufficientdateexception.java代码:
public class insufficientdateexception extends java.lang.exception{ public insufficientdateexception(){} }
|
假设我们保存到d:ejbstatelessdatesrcinsufficientdateexception.java
3.开发bean实现类(statelessdateejb.java):
这个类包含了业务逻辑的所有详细设计细节。会话bean的实现类实现了(implements)javax.ejb.sessionbean所定义的接口,首先我们先熟悉一下sessionbean的定义:
package javax.ejb; impot java.rmi.remoteexception;
public interface sessionbean extends enterprisebean{ public void setsessioncontext(sessioncontext ctx) throws ejbexception,remoteexception; public void ejbremove() throws ejbexception,remoteexception; public void ejbactivae()throws ejbexception,remoteexception; public void ejbpassivate()throws ejbexception,remoteexception; }
|
容器通过这些方法将相关信息通知给bean实例,所有的方法都抛出removeexception方法是为了与1.0规范兼容,之后版本编写的bean只需要抛出ejbexception即可。
setsessioncontext()方法将会话的语境放到对象变量中,容器在结束会话bean或自动超时死亡之前将会自动调用ejbremove()方法,所以在此可以填入用来释放某些资源的代码。当实例被钝化或被激活时,调用ejbactivae()和ejbpassivate()方法,无状态会话bean不会发生这些情况,在下一节将介绍。
一般情况下,习惯将组件实现类的命名规则规定为<bean-name>ejb,所以我们把这个组件类起名为statelessdateejb
类statelessdateejb声明要实现sessionbean的定义,所以,对于statelessdateejb类,我们必须完全实现sessionbean的接口定义。
statelessdateejb.java代码:
import javax.ejb.*; import java.util.date;
public class statelessdateejb implements sessionbean{ public void ejbcreate(){} public void ejbremove(){} public void ejbactivate(){} public void ejbpassivate(){} public void setsessioncontext(sessioncontext ctx){} //计算两个日期之间相隔的天数 public int getdayinrange(date lowerlimitdate,date upperlimitdate) throws insufficientdateexception{ long uppertime,lowertime; uppertime=upperlimitdate.gettime(); lowertime=lowerlimitdate.gettime(); if(uppertime<lowertime) throw new insufficientdateexception(); long result=new long((uppertime-lowertime)/(1000*60*60*24)); return result.intvalue(); } //得到距离2008年奥运会天数 public int getdayforolympic() throws insufficientdateexception { date olympic=new date("2008/01/01"); date today=new date(system.currenttimemillis()); return getdayinrange(today,olympic); } }
|
假设我们保存到d:ejbstatelessdatesrcstatelessdateejb
.java
到此为止我们的bean程序statelessdate已经编写完毕了,使用如下命令进行编译:
cd beanstatelessdatesrc mkdir classes cd src javac -classpath %classpath%;../classes -d ../classes insufficientdateexception.java statelessdate.java statelessdatehome.java statelessdateejb.java
|
如果顺利你将可以在..statelessdateclasses目录下发现有四个类文件。
4.编写部署文件:
一个完整的ejb是由java类和一个描述其特性的ejb-jar.xml文件组成,部署工具将根据这些文件部署到容器中,并自动生成容器所需的残根类。
按照下面个格式编写一个ejb-jar.xml文件,对于dtd介绍此处省去。
ejb-jar.xml文件:
<?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> <description> this is statelessdate ejb example </description> <display-name>statelessdatebean</display-name> <enterprise-beans> <session> <display-name>statelessdate</display-name> <ejb-name>statelessdate</ejb-name> <home>statelessdatehome</home> <remote>statelessdate</remote> <ejb-class>statelessdateejb</ejb-class> <session-type>stateless</session-type> <transaction-type>container</transaction-type> </session> </enterprise-beans> </ejb-jar>
|
假设我们保存到d:ejbstatelessdateclassesmeta-infejb-jar.xml(注意meta-inf必须大写)
现在让我们看看当前的目录结构:
statelessdate <文件夹 designtimesp=22236> classes<文件夹 designtimesp=22239> meta-inf<文件夹 designtimesp=22242> ejb-jar.xml insufficientdateexception.class statelessdate.class statelessdateejb.class statelessdatehome.class src<文件夹 designtimesp=22250> insufficientdateexception.java statelessdate.java statelessdateejb.java statelessdatehome.java
|
部署到应用服务器
在部署之前我们需要将这些类文件和xml文件做成一个jar文件,ejb jar文件代表一个可被部署的jar库,在这个库里,包含了服务器代码与ejb模块的配置。ejb-jar.xml文件被放置在jar文件所指定的meta-inf目录中。我们可以使用如下命令得到ejb
jar文件:
cd d:ejbstatelessdateclasses (要保证类文件在这个目录下,且有一个meta-inf子目录存放ejb-jar.xml文件) jar -cvf statelessdate.jar *.*
|
确保statelessdate.jar文件包括的文件目录格式如下:
meta-inf<文件夹 designtimesp=22303> ejb-jar.xml insufficientdateexception.class statelessdate.class statelessdateejb.class statelessdatehome.class
|
部署工具一般由java应用服务器的制造商提供,在这里我使用了apusic应用服务器,并讲解如何在apusic应用服务器部署这个statelessdate组件。
注意,如果使用其他部署工具,原理是一样的。要使用apusic应用服务器,可以到www.apusic.com上下载试用版。
确定你的apusic服务器已经被启动。 打开"部署工具"应用程序,点击文件->新键工程:
第一步:选择"新建包含一个 ejb组件打包后的ejb-jar模块"选项
第二步:选择一个刚才我们生成的statelessdate.jar文件
第三步:输入一个工程名,可以随意,这里我们输入statelessdate
第四步:输入工程存放的地址,这里我们假设被存放到d:ejbstatelessdatedeploy目录下
完成四个步骤后,如果没有问题将出现statelessdatebean的部署界面,基本的参数配置已经在我们刚才编写的ejb-jar.xml中定义,可以点击部署->部署到apusic应用服务器完成部署工作。
开发和部署测试程序
sessionbean组件是没有任何运行界面的,组件的实例被容器所管理,所以我们要测试这个bean组件,需要写一段测试程序。这里,我们写一段小服务程序(java
servlet)。
关于如何编写servlet我们这里不做介绍。initialcontext 对象用来获取当前servlet小应用程序的语境,方法lookup从组件池中查找一个jndi对象,并取得一个远程主接口的引用,java:comp/env/ejb/statelessdate是我们刚才statelessdate组件的jndi名,请参考ejb-jar.xml中的项。要注意的是,lookup()方法返回的是一个object类型的远程主接口对象的残根,为此需要使用javax.rmi.portableremoteobject的narrow()方法来获取一个具体的对象引用,narrow()方法:第一个参数是lookup()方法返回的对象,第二个参数是要得到的引用类型。我们通过narrow()方法并经过造型得到了一个statelessdatehome对象的实例引用。调用create()方法获取一个statelessdate组件接口的实例引用,然后就可以与本地一样去引用这个实例。使用完毕后不要忘记将实例的引用指向一个null。
下面是提供的代码:
statelessdateservlet .java文件:
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import javax.ejb.*; import javax.naming.initialcontext;
public class statelessdateservlet extends httpservlet{ public void service(httpservletrequest req,httpservletresponse res) throws ioexception { res.setcontenttype("text/html"); printwriter out =res.getwriter(); out.println("<html><head><title>statelessdateservlet</title></head>"); out.println("<body><h2>test result:<hr>"); try{ initialcontext ctx =new initialcontext(); object objref =ctx.lookup("java:comp/env/ejb/statelessdate"); statelessdatehome home=(statelessdatehome) javax.rmi.portableremoteobject.narrow( objref,statelessdatehome.class); statelessdate bean=home.create(); java.util.date lowerlimitdate= new java.util.date(new string(req.getparameter("lowerlimitdate"))); java.util.date upperlimitdate= new java.util.date(new string(req.getparameter("upperlimitdate"))); out.println("the lowerlimitdate:"+lowerlimitdate+"<br>"); out.println("the upperlimitdate:"+upperlimitdate+"<br>"); out.println("the method getdayinrange() result:"+bean.getdayinrange(lowerlimitdate,upperlimitdate)+" days"); out.println("<hr>"); out.println("the method getdayforolympic() result:"+bean.getdayforolympic()+" days"); bean=null; }catch(javax.naming.namingexception ne){ out.println("naming exception caught:"+ne); ne.printstacktrace(out); }catch(javax.ejb.createexception ce){ out.println("create exception caught:"+ce); ce.printstacktrace(out); }catch(java.rmi.remoteexception re){ out.println("remote exception caught:"+re); re.printstacktrace(out); }catch(insufficientdateexception ie){ out.println("insufficientdate exception caught:"+ie); ie.printstacktrace(out); } out.println("</body></html>"); } }
|
假设我们将文件保存到d:ejbstatelessdatesrcstatelessdateservlet.java
使用如下命令编译servlet
cd d:ejbstatelessdate mkdir test cd test mkdir web-inf cd web-inf mkdir classes cd d:ejbstatelessdatesrc javac -classpath %classpath%;../classes/ -d ../test/web-inf/classes statelessdateservlet.java
|
编译成功后将这个servlet部署到与statelessdate同一工程中,在部署前需要我们为部署编写一个web.xml,其中告诉部署工具这个servlet需要参考的一些资源和部署描述,这里我们将定义一个jndi参考:
<ejb-ref> <description></description> <ejb-ref-name>ejb/statelessdate</ejb-ref-name> <ejb-ref-type>session</ejb-ref-type> <home>statelessdatehome</home> <remote>statelessdate</remote> <ejb-link>statelessdate</ejb-link> </ejb-ref>
|
告诉部署工具,这个web模块需要参考的资源jndi名称,被指定参考一个sessionbean组件。
web.xml文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<!doctype web-app public '-//sun microsystems, inc.//dtd web application 2.3//en' 'http://java.sun.com/dtd/web-app_2_3.dtd'>
<web-app> <icon> <small-icon></small-icon> <large-icon></large-icon> </icon> <display-name>statelessdate</display-name> <description></description> <context-param> <param-name>jsp.nocompile</param-name> <param-value>false</param-value> </context-param> <context-param> <param-name>jsp.usepackages</param-name> <param-value>true</param-value> <description></description> </context-param> <ejb-ref> <description></description> <ejb-ref-name>ejb/statelessdate</ejb-ref-name> <ejb-ref-type>session</ejb-ref-type> <home>statelessdatehome</home> <remote>statelessdate</remote> <ejb-link>statelessdate</ejb-link> </ejb-ref> </web-app>
|
假设我们将文件保存到d:ejbstatelessdate estweb-infweb.xml
下面我们要部署这个servlet到j2ee服务器。j2ee web应用可以包括java servlet类、javaserver page组件、辅助的java类、html文件、媒体文件等,这些文件被集中在一个war文件中。其中war结构具有固定的格式,根目录名为web-inf,同一目录下应该有一个web.xml文件,用来描述被部署文件的部署信息,jsp、html等文件可以放置在这个目录下,同时web-inf目录下可能存在一个classes目录用于存放servlet程序,如果引用了一些外部资源,则可以被放置到web-inflib目录下。使用下面的命令生成这个servlet测试程序的war文件:
cd d:ejbstatelessdate est jar -cvf statelessdate.war *.*
|
确保statelessdate.war文件包括的文件目录格式如下:
web-inf<文件夹 designtimesp=22563> classes<文件夹 designtimesp=22565> statelessdateservlet.class web.xml
|
成功编译后,将这个servlet一同部署到statelessdate工程中,我们回到"部署工具",点击编辑-->填加一个web模块,选择我们刚刚编译成的statelessdate.war文件
点击部署->部署到apusic应用服务器完成部署工作。
运行测试程序
打开浏览器,在浏览器中输入:
http://localhost:6888/statelessdate/servlet/statelessdateservlet?lowerlimitdate=2001/01/01&upperlimitdate=2001/12/31
localhost-web server的主机地址 :6888-应用服务器端口,根据不同的应用服务器,端口号可能不同 /statelessdate-部署servlet时指定的www根路径值 /servlet-ejb容器执行servlet的路径 /statelessdateservlet-测试程序 ?lowerlimitdate-参数1,起始日期 &upperlimitdate-参数2,结束日期
|
如果运行正常应该能够看到下面的结果
<图2-2>