状态会话Bean和无状态会话Bean的区别
状态会话Bean也是要实现SessionBean接口,但和无状态会话Bean有所区别。
setSessionContext(SessionContext ctx):
将Bean和会话上下文关联起来。Bean就可以查询上下文有关它的当前事务状态、当前安全状态。该方法的实现状态会话Bean和无状态会话Bean没有区别
ejbCreate()方法:初始化会话Bean。可以定义几个ejbCreate()方法,而且每一个使用不同的参数。在会话Bean中必须至少提供一个ejbCreate()方法。
但是,在状态会话Bean中,可以定义几个ejbCreate()方法,而且每一个使用不同的参数。
无状态会话Bean却只可以定义一个空的ejbCreate()方法,而且这个方法不带任何参数。
因为即使有参数,而且Bean用这些参数对其进行初始化,Bean也绝对不会在接下来的调用中记住它是如何初始化其自身的,因为它是无状态的。
ejbPassivate():在Bean被钝化之前立即调用;
ejbActivate():在Bean激活之前立即调用。
这两个方法不适用于无状态的会话Bean。
无状态会话Bean实例都相同,所以容器可以将它动态的分配给任意一个客户端,但有状态会话Bean则不一样,因为每个会话Bean都存储一个特定的客户状态。因此一个客户请求就建立一个会话Bean,但若客户很多,肯定不现实。内存,数据库连接等资源都是有限的。
因此EJB容器采用激活/钝化的方式来管理状态会话Bean。
为了限制会话Bean实例在内存中的数量,EJB容器将状态会话Bean交换出去,其对话状态保存在硬盘或其他存储器中,这种方法叫做钝化。状态会话Bean被钝化后,对话状态被安全地存储起来,使得内存等资源能被重新使用。当被钝化的Bean原先的客户端对其触发方法进行调用时,被钝化的对话状态重新交换给Bean,这种方法叫做激活,然后,Bean恢复了与原先的客户端的对话。
注意,接收激活状态的Bean可能不是原先的Bean实例。但是,这并没有关系,因为新的Bean实例从原先的Bean实例钝化时的那一点重新开始对话。
大多数EJB容器采用一种叫做最近最少被使用钝化策略。只要Bean没有被调用,钝化随时都可能发生。什么时候钝化完全由EJB容器决定。当有客户请求时就激活。
激活/钝化对无状态会话Bean没有用处,因为它没有状态被钝化或激活,可以被EJB容器随意地从内存中清除。
通常在ejbPassivate()方法内释放所有Bean可能占用的资源,包括数据库连接,打开的套接字,打开的文件以及其他没有必要保存在磁盘或者无法明显地采取对象序列化方式保存的资源。
ejbActivate()方法获得所有Bean需要的资源,例如那些在ejbPassivate()方法中释放的那些资源。
ejbRemove():在Bean从内存中删除前立即通过容器调用。该方法对(无)状态会话Bean都一样。
状态会话Bean实例
Cart用来模拟购物车, CartEJB有三个实例变量:customerName(顾客姓名), customerId(顾客ID), contents(用来保存顾客购买的书)。还有添加、删除购买书的功能
(1)CartEJB类源文件
与其他所有的会话Bean一样, CartEJB类必须满足下列要求:
A:实现SessionBean接口
B:类定义为Public
C:类不能定义为abstract或者final
D:实现一个或多个EjbCreate方法
E:实现商业方法
F:含有一个无参数的public构造器
G:不能定义finalize方法
import java.util.*;
import javax.ejb.*;
public class CartBean implements SessionBean {
//定义实例变量,代表了购物车应用程序的会话状态
String customerName;
String customerId;
Vector contents;
public void ejbCreate(String person) throws CreateException {
if (person == null) {
throw new CreateException("Null person not allowed.");
}
else {
customerName = person;
}
customerId = "0";
contents = new Vector();
}
public void ejbCreate(String person, String id) throws CreateException {
if (person == null) {
throw new CreateException("Null person not allowed.");
}
else { customerName = person;}
IdVerifier idChecker = new IdVerifier();
if (idChecker.validate(id)) {
customerId = id;}
else {
throw new CreateException("Invalid id: " + id);}
contents = new Vector();}
public void addBook(String title)
{
contents.addElement(title);
}
public void removeBook(String title) throws BookException {
boolean result = contents.removeElement(title);
if (result == false) {
throw new BookException(title + " not in cart.");
}}
public Vector getContents() {
return contents;}
public CartBean() {}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void setSessionContext(SessionContext sc) {}
}
SessionBean接口声明了ejbRemove() 、 ejbActivate() 、 ejbPassivate() 和setSessionContext方法,但CartEJB类不使用这些方法,但它必须实现这些方法,因为它们被声明在SessionBean接口中,所以在CartEJB类中这些方法的体为空。
ejbCreate方法:
由于企业Bean运行在EJB容器内部,客户机不能直接实例化bean,仅仅EJB容器可以实例化bean,就是调用ejbCreate方法。在实例化期间,实例程序执行下面的步骤:
(1)客户机调用home对象上的create方法
Cart shoppingCart = home.create("Duke DeEarl","123");
(2)EJB容器实例化企业Bean
(3)EJB调用CartEJB中的相应的ejbCreate方法。
public void ejbCreate(String person, String id) throws CreateException {
if (person == null) {
throw new CreateException("Null person not allowed.");
}
else { customerName = person; }
IdVerifier idChecker = new IdVerifier();
if (idChecker.validate(id)) {
customerId = id; }
else {
throw new CreateException("Invalid id: " + id);}
contents = new Vector(); }
典型地,ejbCreate方法实例化企业Bean的状态,例如,通过create方法传递的参数来初始化customerName和customerId变量。
企业Bean可能有一个或多个ejbCreate方法,它们应该满足下列要求:
A:访问控制修饰符必须为public
B:返回类型必须为Void
C:参数必须是Java RMI的合法类型
D:修饰符不能为static或void
另外, ejbCreate方法要抛出CreateException异常(输入参数无效)。若有其他异常也应该抛出
商业方法:
客户机调用create方法返回的远程对象引用上的商业方法。
Cart shoppingCart = home.create("Duke DeEarl","123");
shoppingCart.addBook("The Martian Chronicles");
shoppingCart.removeBook("Alice in Wonderland");
bookList = shoppingCart.getContents();
CartEJB类实现商业方法的代码如下:
public void addBook(String title) { contents.addElement(title);}
public Vector getContents() {return contents;}
public void removeBook(String title) throws BookException {
boolean result = contents.removeElement(title);
if (result == false) { throw new BookException(title + " not in cart."); }}
商业方法必须遵循下面的规则:
A:方法名不能与EJB结构定义的相冲突,例如,不能调用商业方法ejbCreate或ejbActivate。
B:访问控制修饰符必须为public
C:参数和返回类型必须是Java RMI的合法类型。
D:修饰符不能是static或final。
Throws子句可以包含我们为应用程序定义的异常,例如,removeBook方法,如果书不在购物车中,则抛出BookException异常。
BookException异常的源代码为:
public class BookException extends Exception {
public BookException() {
}
public BookException(String msg) {
super(msg);
}
}
Remote接口
import java.util.*;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Cart extends EJBObject {
public void addBook(String title) throws RemoteException;
public void removeBook(String title) throws BookException, RemoteException;
public Vector getContents() throws RemoteException;
}
远程接口扩展定义EJBObject,定义了客户机可以调用的商业方法。远程接口中方法的定义必须遵循下面的规则:
A:远程接口中的每一个方法必须与企业Bean类中实现的方法匹配。
B:远程接口中的方法必须与企业Bean类中对应的方法相同。
C:参数和返回类型必须是有效的RMI类型
D:throws子句必须包括java.rmi.remoteException
Home接口:
import java.io.Serializable;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface CartHome extends EJBHome {
Cart create(String person) throws RemoteException, CreateException;
Cart create(String person, String id) throws RemoteException,
CreateException;
}
home接口扩展EJBHome接口,home接口的目的是定义客户机可以调用的create方法,例如,CartClient程序,调用create方法:
Cart shoppingCart = home.create("Duke DeEarl","123");
Home接口中的每一个create方法都对应一个bean类中的ejbCreate方法,CartEJB中的ejbCreate方法如下:
public void ejbCreate(String person) throws CreateException
public void ejbCreate(String person, String id) throws CreateException
CartEJB中和CartHome接口中的create方法,有着重要的不同,home接口的create方法定义规则不同:
A: create方法的参数和返回类型必须是有效的RMI类型
B: create方法返回企业Bean的远程接口类型(但是ejbCreate方法返回void)
C: create方法的throws子句必须包括java.rmi.remoteException和javax.ejb.CreateException
CartEJB有两个help类:BookException和idVerifier。 idVerifier
用来验证ejbCreate方法中customerID的有效性。内容如下:
public class IdVerifier {
public IdVerifier() { }
public boolean validate(String id) {
boolean result = true;
for (int i = 0; i < id.length(); i++) {
if (Character.isDigit(id.charAt(i)) == false)
result = false;
}
return result; }}
客户机:CartClient
import java.util.*;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
public class CartClient {
public static void main(String[] args) {
try {
Context initial = new InitialContext();
Object objref = initial.lookup("java:comp/env/ejb/SimpleCart");
CartHome home =
(CartHome)PortableRemoteObject.narrow(objref,
CartHome.class);
Cart shoppingCart = home.create("Duke DeEarl","123");
shoppingCart.addBook("The Martian Chronicles");
shoppingCart.addBook("2001 A Space Odyssey");
shoppingCart.addBook("The Left Hand of Darkness");
Vector bookList = new Vector();
bookList = shoppingCart.getContents();
Enumeration enumer = bookList.elements();
while (enumer.hasMoreElements()) {
String title = (String) enumer.nextElement();
System.out.println(title);
}
shoppingCart.removeBook("Alice in Wonderland");
shoppingCart.remove();
System.exit(0);
} catch (BookException ex) {
System.err.println("Caught a BookException: " + ex.getMessage());
System.exit(0);
} catch (Exception ex) {
System.err.println("Caught an unexpected exception!");
ex.printStackTrace();
System.exit(1); }} }
另一个状态会话Bean的例子(用JB+WebLogic实现)
本例用有状态会话Bean(Cart)模拟一个购物车,实现购物车的以下功能:
l 记录用户的个人信息,如姓名、信用卡号
l 记录所购买的商品的名称、价格
l 删除存于购物车的商品
l 计算所购买商品的总价格
l 确认所购买商品
为了满足购物车中的第一条要求,可以在此会话Bean中添加两个属性,分别代表用户姓名和信用卡号:为了满足第二条要求,在Bean中添加一个代表商品的属性;最后添加4个方法,分别用来实现添加商品、删除商品、计算总价和确认所购商品的功能。商品可以用一个类Item表示。Item的定义如下:(注意该类要实现Serializable接口)
public class Item implements java.io.Serializable
{
private String _title; //商品名称
private float _price; //商品单价
public Item(String title,float price)
{ _title=title; _price=price; }
public String getTitle() //获得商品名称
{ return _title; }
public float getPrice() //获得商品单价
{ return _price;
} }
该实例的开发步骤如下:
(1)在Jbuilder中建立一个名为CartProject的工程
(2)建立EJB Module,输入EJB模块名为:myCart,jar文件名为: myCart
(3)将代表商品的Item类加入工程
单击File-àNew-à选择General选项卡-à选择Class-à输入:
package:cartproject
Class Name:Item
把上面的Item.java代码加入新建的文件中
(4)创建会话Bean
鼠标右击EJB设计区-à选择Create EJB―――>Session Bean-à输入:
Bean name:cart
Interfaces:remote
Session type:Stateful
(5)添加属性
有状态会话Bean应具有三个属性:代表购物者姓名的cardHolderName(string类型)、代表信用卡号的creditCardNumber(String类型)和代表所购商品的items(Vector类型)
在EJB 设计区中鼠标右击EJB图标-à选择Add-àField-à输入:
Field name:(属性名称)
Type(数据类型)
添加三个属性
(6)修改ejbCreate方法(Bean的构造方法)
有状态的会话Bean通常有属性,要求Bean在建立时要给属性赋值。在ejbCreate增加两个参数,这两个参数分别和持卡人姓名和信用卡号两个属性相对应。
在EJB设计区中单击Cart-àejbCreate在Input parameters中输入:String _cardHoldName,String _creditCardNumber。
(7)增加4个完成业务操作的方法
增加方法addItem
该方法的功能是把参数指定的商品列表(items)。在EJB设计器中用鼠标右击BEAN项,选择add-àMethod-à输入:
Method name:addItem
Return type:void
Input parameters:Item item
Interfaces:remote
增加方法removeItem,该方法的功能是从商品列表(items)中去掉指定的商品。
Method name:removeItem
Return type:void
Input parameters:Item item
Interfaces:remote
I. 增加方法getTotalPrice,该方法的功能是计算所购商品的总价
Method name:getTotalPrice
Return type:float
Input parameters:
Interfaces:remote
增加方法purchase,该方法输出一个确认信息。
Method name:purchase
Return type:void
Input parameters:
Interfaces:remote
给Bean添加代码
此时Bean的代码如下:
package cartproject;
import javax.ejb.*;
import java.rmi.*;
import java.lang.*;
import java.util.*;
import java.io.Serializable;
public class cartBean implements SessionBean
{
SessionContext sessionContext;
java.lang.String cardHolderName;
java.util.Vector items=new java.util.Vector();
java.lang.String creditCardNumber;
public void ejbCreate(String _cardHolderName,String _creditCardNumber) throws CreateException
{
cardHolderName=_cardHolderName;
creditCardNumber=_creditCardNumber;
}
public void ejbRemove(){
}
public void ejbActivate(){
}
public void ejbPassivate(){
}
public void setSessionContext(SessionContext sessionContext){
this.sessionContext=sessionContext;
}
/**
public void addItem(Item item){
System.out.println("\taddItem("+item.getTitle()+"):"+this);
items.addElement(item);
}
public void removeItem(Item item){
System.out.println("\taddItem("+item.getTitle()+"):"+this);
Enumeration elements=items.elements();
while(elements.hasMoreElements())
{
Item current=(Item)elements.nextElement();
if (item.getClass().equals(current.getClass())&& item.getTitle().equals(current.getTitle()))
{ items.removeElement(current);
return;
}
}}
public float getTotalPrice()
{
System.out.println("\tgetTotalPrice():"+this);
float totalPrice=0f;
Enumeration elements=items.elements();
while (elements.hasMoreElements())
{
Item current=(Item)elements.nextElement();
totalPrice+=current.getPrice();
}
return (long)(totalPrice*100)/100f;
}
public void purchase(){
System.out.println("\tpurchase():"+this);
}
}
(9)生成jar文件
在工程窗口中鼠标右击myCart-à选择make
(10)启动weblogic Server
(11)部署Cart到Weblogic server
鼠标右击myCart-à选择Deploy Options for myCart.jar-àdeploy
(12)建立客户端程序
通过向导产生客户端程序:File-àNew-à选择Enterprise选项卡-à选择EJB TestClient-à选择类型为application
(13)修改客户端程序的main方法
修改后的客户端程序main方法为:
public static void main(String args[])
{ cartTestClient1 client=new cartTestClient1();
String cardHolderName="Jack B.Quick";
String creditCardNumber="1234-5678-9012-3456";
client.create(cardHolderName,creditCardNumber);
Item knuthBook1=new Item("The Art of VB.net",49.95f);
Item knuthBook2=new Item("The Art of C#",49.95f);
client.addItem(knuthBook1);
client.addItem(knuthBook2);
client.purchase();}
状态会话Bean的生命周期
图显示了一个有状态会话Bean的生命周期中的三个阶段:从不存在到准备就绪到钝化,然后原路返回。
1。客户端调用create方法(该方法在Home或者Local Home接口中声明)就开始了一个有状态会话Bean的生命周期(在此之前是Does Not Exist,不存在阶段)。
2。EJB容器产生一个Bean的实例然后调用Bean类的setSessionContext和ejbCreate方法。这时该Bean实例进入准备就绪状态(Ready),它的商业方法可以被客户端调用了。
3。在就绪状态,EJB容器可能会把该Bean实例从内存中移出到外存以钝化该实例(EJB容器一般采用最近最少使用原则来钝化内存中的Bean实例)。其实就是把内存中EJB容器认为暂时不会用到的Bean实例转移到外存中,以提高内存使用效率。在钝化Bean实例之前,EJB容器会先调用ejbPassivate方法,所以你如果想在钝化之前做什么动作的话就可以在该方法里实现。
4。如果钝化期间有客户端调用该Bean实例的商业方法,EJB容器将该实例读入内存激活它回到就绪状态,同时调用该Bean类的ejbActivate方法。所以你要在Bean实例被激活的时候做点什么动作的话,就在这个方法实现。
5。在企业Bean的生命周期最后,客户端调用remove(同样是Home或者Local Home接口里的)方法,然后容器调用Bean类的ejbRemove方法结束了一个实例的生命。回到“不存在”状态。
注意:在这些生命周期方法中,你可以在客户端编码调用的只有两个:create和remove方法,其他方法都是由EJB容器来调用的。
无状态会话Bean的生命周期
因为无状态会话Bean不需要保存任何状态信息,所以它不需要钝化,在不用的时候直接删掉就好了。所以它的生命周期只有两个阶段:不存在(Does Not Exist)和就绪(Ready)如下图:
何时需要会话Bean
通常,在出现以下几种情况时你需要用会话Bean:
v 在任何给定时间,只有一个客户端访问这个Bean的实例。
v Bean的状态并不需要持久保存,只在一个时间段(可能是几小时)内保持。
v 在以下情况下,建议采用有状态会话Bean:
v Bean需要描述一个于特定客户端的会话状态
v Bean需要在客户端的多个方法调用之间保存调用信息
v Bean作为应用程序的其他组件和客户端的中介者,呈现一个简单化的视图给客户端
v 在调用接口里,Bean管理很多企业Bean的工作流。
v 如果你的应用符合以下特性,为了得到更高的性能你应该选择无状态会话Bean:
v Bean的状态不包含客户端相关的数据
v 在一个单一方法调用中,Bean已经可以为客户端完成所需要的工作。例如你可以用无状态会话Bean发一封邮件确认网络订单。
v Bean需要从数据库获取一些客户端经常访问的只读数据。你可以用这样的Bean来访问数据表中代表这个月已经卖出的产品的行。
v
v 企业Bean的命名约定
| v 项目 | v 约定 | v 实例 |
| v 企业Bean命名(DD) | v <name>EJB | v AccountEJB |
| v EJB的存档文件JAR命名(DD) | v <name>JAR | v AccountJAR |
| v 企业Bean主类命名 | v <name>Bean | v AccountBean |
| v Home接口命名 | v <name>Home | v AccountHome |
| v Remote接口命名 | v <name> | v Account |
| v Local home接口命名 | v Local<name>Home | v LocalAccountHome |
| v Local接口命名 | v Local<name> | v LocalAccount |
| v 抽象数据模式命名(DD) | v <name> | v Account |
v
几个易混淆的问题
1)EJB容器
EJB容器负责管理您的Beans。容器在必要时可以通过调用Bean的必需的管理方法与Bean进行交互。
EJB容器就好比剧院里的幕后舞台总监,为在舞台上表演的演员能成功地演出而提供所需灯光和背景。不论是演员还是观众都不直接与舞台总监进行直接交互。
2)实例调度的概念
EJB容器准确、动态地实例化、销毁和重用Bean组件。
当有某个客户代码请求某种类型的Bean组件,而这个Bean组件还没有被调入内存中时,EJB容器就会在内存中实例化一个该Bean组件的对象,响应客户请求。另一方面,如果这个被请求的Bean组件已经调入内存,则不在实例化这个Bean组件。当内存资源少时,容器可能将某个用户使用的Bean组件分配给其他用户(如当用户在思考时,用户可以使用Bean实例为其他用户服务,节省了系统资源),或销毁某个不被任何客户使用的Bean组件。
这种管理方式叫做实例池调度。
3)在调用create()和remove()方法时会发生什么?
我们已经知道,容器负责生成和销毁Bean,而不是客户端负责。可是,既然EJB容器负责管理Bean的生命周期,那为什么还要Home接口指定create()方法和remove()方法?
注意,这些方法是用于生成和清除EJB对象的,它并不等同于真正的Bean生成和清除。客户端也不必在乎真正的Bean是否生成或清除,它们只在乎有没有EJB对象能够被调用。
至于Beans,由于EJB对象的支持,怎样被调度和重用,都是不必由客户端关心的。
因此,调试EJB程序时,当调用Home对象的create()方法和remove()方法时,假如Bean并未创建或销毁,不用感到吃惊。
Bean在什么时候生成,取决于EJB容器采取的策略。