专稿:实战EJB之二 开发会话Bean(无状态会话Bean)

发表于:2007-06-22来源:作者:点击数: 标签:
实战EJB系列 在以后的日子里,将由Jackliu向大家陆续提供一系列EJB教程,有学习EJB的朋友请同步参考EJB相关书籍,实战系列将以例程的方式帮助你理解这些基本的概念,其中将包括: 点击查看大图 所有章节完毕后将制作成pdf电子文档,供大家下载。 实战EJB之二

   

实战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有两种状态:存在或不存在。



专稿:实战EJB之二 开发会话Bean(无状态会话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至少包括四个步骤:



  1. 开发主接口
  2. 开发组件接口
  3. 开发Bean实现类
  4. 编写部署文件

注意:本节假设你使用的Windows操作系统。如果使用其他操作系统,可能影响到存储路径和JDK命令,但这与程序代码和部署文件内容无关。


1.开发主接口(StatelessDateHome.java):


是由Bean开发人员编写的一个Bean的主接口(interface)程序,负责控制一个Bean的生命周期(生成、删除、查找Bean)。只需要开发人员给出一个主接口类,类方法的实现由容器来完成。
主接口扩展了javax.ejb.EJBHome接口,参考avax.ejb.EJBHome接口定义如下:



clearcase/" target="_blank" >cccccc border=1>






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,结束日期



如果运行正常应该能够看到下面的结果


 



专稿:实战EJB之二 开发会话Bean(无状态会话Bean)(图三)


<图2-2>


 


 



 


 


原文转自:http://www.ltesting.net