4.1 什么是会话EJB
会话EJB实现了运行于服务器上的业务逻辑。对客户端来说,一个会话对象是一个非持久性的对象。对会话对象的一种理解是把它看成运行在服务器上的客户端程序的扩展。
会话EJB一个最显著的特征是它是依附于客户端的。从会话EJB的产生和销毁都由客户端控制。会话EJB的状态不会被永久保存。也就是说,会话EJB不具备持久性。
会话EJB在实际应用中的一个典型例子是订单的处理。例如对于某个登录的用户,他进行订单的操作由若干步骤完成,在所有步骤完成之前,并不是把每个步骤的操作都记录到永久性的存储器(如javascript:;" onClick="javascript:tagshow(event, '%CA%FD%BE%DD%BF%E2');" target="_self">数据库)中,而是由会话EJB暂保存这些状态数据,并完成所有步骤之后,再把所有的状态数据提交到相应的处理模块。另一个类似的例子是宠物商店中的购物车(关于宠物商店的详细介绍参见第14章),用户把选购的商品放入购物车中,选完所有商品后才一起结账,一个购物车可以设计成一个会话EJB。
会话EJB的状态指的是会话EJB所保存的某个客户端的状态。有状态会话EJB指的是能够保持某个客户端状态的会话EJB;无状态会话EJB指的是不保存某个客户端状态的会话EJB。因此,从这个意义上说,无状态会话EJB并不是完全的没有状态,它只是没有保存特定客户端的状态而已。无状态会话EJB适用在不保持客户端的状态和状态不随客户端的不同而不同的情况。例如计算一个实数的平方根(不保存状态);有状态会话EJB的例子是把商品类型的数据库映射成无状态EJB(对于不同客户端,商品类型是一致的)。很多资料认为会话EJB是有状态的,而把无状态会话EJB看做是它的一个特殊情况。之所以采用无状态会话EJB。仅仅是为了在特殊的情况下,提高会话EJB的效率。也就是说,无状态会话EJB从响应速度和占用资源方面比有状态会话EJB效率高。
在下面的章节中会进一步讨论两种会话EJB的异同。
4.2 会话EJB编程模型
如图4-1所示,会话EJB的编程模型简述如下:客户端通过Bean的远程接口访问会话对象。而实现这个远程接口的Java');" target="_self">Java对象是一个会话对象EJBObject。一个EJBObject会话对象是一个能被客户端通过标准的远程方法调用Java API访问的远程Java对象。从创建到销毁,会话对象存在于容器中。容器对客户端来说是透明的,容器为Session对象提供了安全、并发、事务、缓存和其他的服务,每个Session对象拥有一标识。一般情况下,该标识随着服务器的停止而结束。Session Bean的客户端视图与位置无关,一个和Session对象运行在同一个JVM的客户端和运行在相同或不同机器上的不同JVM的客户端使用相同的APIs。一个会话对象的客户端可能是另外一个部署在相同或不同容器上的企业Bean,或者是任意的Java程序,如应用程序、Applet,或Servlet。一个Session Bean的客户端视图也可以映射成非Java客户端环境,如CORBA客户端,这不是由Java程序语言写的。
多企业Bean可以安装在一个容器中。容器允许客户端通过JNDI查找安装好的主接口。一个Session Bean的主接口提供了这个Bean创建、删除的方法。一个会话对象的客户端视图是一致的,它不随session Bean和容器的不同而不同。
下面深入讨论其中相关的问题。
4.2.1 定位Bean的主接口
多个EJB可以部署在同一个容器中。客户端可以使用JNDI查找指定企业Bean的主接口。
客户端使用JNDI来查找会话Bean的主接口。例如,一个Cart会话Bean的主接口可以使用如下代码查找。
Context initialContext = new InitialContext();
CartHome cartHome = (CartHome)javax.rmi.PortableRemoteObject.narrow(
initialContext.lookup("java:comp/env/ejb/cart"),CartHome.class);
一个客户端的JNDI命名空间可以配置起来包含网络上在不同机器上的、不同EJB容器中的EJB的主接口。而EJB容器的实际位置对使用企业Bean的客户端来说是透明的。
实现主接口的分布式对象(即EJBHome对象)的生命周期是与EJB容器相关的。客户端应用程序可以获取主接口,而且在客户端的生命周期中多次使用。客户端可以把主接口的引用传递给另一个应用程序,而接受的应用程序可以使用主接口对象的引用,就和通过JNDI获取的一样。
容器实现了安装在容器中的EJB的主接口。实现会话Bean主接口的对象叫做EJBHome对象。容器使会话Bean的主接口通过JNDI对客户端可用。
4.2.2 创建并使用会话对象
主接口定义一个或多个create(...)方法,每个方法定义了一种创建会话对象的方式,create方法的参数用来初始化创建的会话对象的状态。
下面这个例子演示了有两个create<METHOD>(...)方法的主接口:
public interface CartHome extends javax.ejb.EJBHome{
Cart create(String customerName,String aclearcase/" target="_blank" >ccount)
throws RemoteException,BadAccountException,CreateException;
Cart createLargeCart(String customerName,String account)
throws RemoteException,BadAccountException,CreateException;}
下面演示了客户端怎样使用主接口的create<METHOD>(...)方法创建远程对象:
cartHome.create("John","7506");
客户端从来不会直接访问会话Bean类的实例。客户端使用会话Bean的远程接口来访问会话Bean的实例。实现会话Bean的远程接口的实例由容器提供,它的实例叫做会话EJBObjects。
会话EJBObject支持:
·对象的业务逻辑方法
会话EJBObject代理对会话Bean实例的业务逻辑方法的调用。
·javax.ejb.EJBObject接口定义的方法。这些方法允许客户端:
·获取会话对象的主接口
·获取会话对象的句柄
·测试会话对象是否与另一个是同一个
·删除会话对象
javax.ebj.EJBObject接口中定义的方法是由容器实现的,而并不是由会话Bean类代理的。
一个会话Bean运行期对象的例子如图4-2所示。
客户端使用Cart主接口的create方法创建一个Cart会话对象。客户端使用这个会话对象来填写购物单并购买相关内容。
假设用户开始购买后终止一两天,然后再去完成.客户端可以通过获取会话对象的句柄,把句柄序列化到持久性的存储器中,然后重新用它来创建最初的Cart来实现。
对下列例子,开始,在JNDI中查找Cart的主接口,然后使用主接口来创建一个Cart会话对象并加入一些购物项:
CartHome cartHome = (CartHome)javax.rmi.PortableRemoteObject.narrow{
initialContext.lookup(...),CartHome.class);
Cart cart = cartHome.createLargeCart(...);
cart.addItem(66);
cart.addItem(22);
下面我们决定稍候完成这次购物,因此要序列化这个Cart会话对象的句柄并把它存在一个文件里:
Handle cartHandle = cart.getHandle();
//serialize cartHandle,store in a file...
最后我们从文件中反序列化句柄,并重新创建cart会话对象的引用,并购买购物单里的商品:
Handle cartHandle = ...;//deserialize from a file...
Cart cart = (Cart)javax.rmi.PortableRemoteObject.narrow(
cartHandle.getEJBObject(),Cart.class);
cart.purchase();
cart.remove();
4.2.3 删除会话对象
客户端可以使用javax.ejb.EJBObject接口的remove()方法删除一个会话Bean,或者使用接口javax.ejb.EJBHome中定义的remove(Handle hanele)方法。
因为会话对象没有主键供客户端访问,调用方法javax.ejb.EJBHome.remove(Object primaryKey)会抛出异常javax.ebj.RemoveException。
拥有会话引用的客户端可以做如下事情:
·调用会话对象远程接口中定义的业务方法
·获取会话对象主接口的引用
·获取会话对象的句柄
·在客户端的范围内把引用作为参数或返回值
·删除会话对象,会话对象也可以在生命周期到时被容器自动删除。
引用一个不存在的会话对象是无效的。试图调用一个存在的会话对象会抛出异常:java.rmi.NoSuchObjectException。
4.2.4 会话标识
会话对象作为创建它的客户端的私有资源,在客户端看来是匿名的。和实体对象相反,实体对象有主键作为标识,会话对象隐藏它们的标识。结果是,调用会话Bean的EJBObject.getPrimaryKey()方法和EJBHome.remove(Object primaryKey)方法会抛出java.rmi.RemoteException异常。
如果调用会话Bean的EJBMetaData.getPrimaryKeyClass()方法,会抛出异常java.lang.RuntimeException。
既然所有的会话隐藏了它它们的标识,就不需要为它们提供finder方法。因此,会话Bean的主接口没有定义任何finder方法。
会话对象句柄在客户端生命结束后可以继续保持,可以把它序列化到一个持久的存储器中。当句柄被反序列化后,会话对象可以正常工作,只要服务器上的会话对象依然存在(时间到或服务器关闭会销毁会话对象)。
客户端代码必须使用javax.rmi.PortableRemoteObject.narrow(...)方法来把调用句柄上的getEJBObject()方法的结果转化成远程接口类型。
自动允许句柄的所有者来调用远程对象的方法。当会话对象的引用从一个句柄中获得,并当会话对象的方法被调用时,容器会基于调用的原则进行访问检查。
1. 有状态会话EJB标识
有状态会话对象具有惟一的标识,在容器创建的时候被分配。
客户端通过调用isIdentical(EJBObject otherEJBObject)方法能判断两个会话对象引用是否一致。
下面演示了有状态会话对象中isIdentical方法的使用:
FooHome fooHome = ...;//obtain home of a stateful session bean
Foo foo1 = fooHome.create(...);
Foo foo2 = fooHome.create(...);
if (foo1.isIdentical(foo1)){//返回true
...
}
if (foo1.isIdentical(foo2)){//返回false
...
}
2. 无状态会话EJB标识
同一标识对象创建的所有无状态会话Bean拥有同样的标识,这个标识是容器分配的。如果无状态会话EJB被部署了多次(每次部署导致一个不同的home的创建),不同的主接口的会话对象就会有不同的标识。
当用来比较同一个无状态什么Bean的不同对象应用时,方法isIdentical(EJBObject otherEJBObject)总会返回true。
下面例子演示了stateless session object的isIdentical方法的使用。
FooHome fooHome = .../obtain home of a stateless session bean
Foo foo1 = fooHome.create();
Foo foo2 = fooHome.create();
if (foo1.isIdentical(foo1)){//返回true
...
}
if (foo1.isIdentical(foo2)){//仍然返回true
...
}
3. getPrimaryKey()
会话对象的标识对客户端来说是透明的,调用会话EJBObject的getPrimaryKey()方法会抛出java.rmi.RemoteException异常。