在会话bean综述中,描述了无状态和有状态bean的区别在于客户端和服务器之间交互形式不同。对于无状态会话bean,交互的开始和结束都在同一个方法中。有时客户端需要发出多个服务请求(需要调用多个方法),而每个请求需要访问或者考虑前面的请求结果。有状态会话bean的出现就是为了处理这种情况,通过向客户端提供一个专用的服务(某一个可以保留前面状态的bean),当客户端获得bean的引用时启动该服务,并且只有当客户端选择结束会话时才结束。回到饭店吃饭的例子就是当你点餐的时候一个服务员在为你服务,直到你离开饭店这个服务员才会被释放,吃饭过程中全部是同一个人在为你服务。
说到有状态会话bean,不得不提的一个例子就是购物车的例子,这是一个典型的有状态会话bean的例子。客户端获取购物车的引用,启动会话。在用户会话期间,客户端在购物车中添加或删除项目,其中购物车维护特定于客户端的状态。然后,当会话结束时,客户端完成购买,购物车才会被删除。
这与在普通的Java应用程序代码中使用一个非委托的java对象没什么不同。我们创建了一个类的实例,调用对象上的操作(set方法)改变实例中的属性值,然后当不再需要对象时释放它。有状态会话bean与上面提到的普通Java对象的区别是服务器管理实际的对象实例,而客户端与该实例通过bean的业务接口进行间接地交互。注意这里客户端知道的仅仅是一个接口,而不是像普通Java应用程序中那样拿到这个实例的真正引用。
有状态会话bean提供了无状态会话bean可用功能的一个超集。无状态会话bean所包含的功能,如远程接口功能,同样也适用于有状态会话bean。
1.定义有状态会话bean
以典型的购物车的例子作为代表定义有状态会话bean,类似于无状态会话bean,有状态会话bean是由一个或多个有单一的bean类实现的业务接口组成。购物车bean的一个本地业务接口示例如下代码所示
public interface ShoppingCart { public void addItem(String id, int quantity); public void removeItem(String id, int quantity); public Map<String, Integer> getItems(); public void checkout(int paymentId); public void cancel(); }
下面代码显示了实现ShoppingCart接口的bean类。通过@Stateful注解标记这个bean类,来告诉服务器该类是一个有状态会话bean。
@Stateful public class ShoppingCartBean implements ShoppingCart { private HashMap<String,Integer> items = new HashMap<String,Integer>(); public void addItem(String item, int quantity) { Integer orderQuantity = items.get(item); if (orderQuantity == null) { orderQuantity = 0; } orderQuantity += quantity; items.put(item, orderQuantity); } public void removeItem(String item, int quantity) { Integer orderQuantity = items.get(item); if (orderQuantity == null) { return; } orderQuantity -= quantity; if (orderQuantity > 0) { items.put(item, orderQuantity); } else { items.remove(item); } } public Map<String, Integer> getItems() { return items; } @Remove public void checkout(int paymentId) { // store items to database // ... } @Remove public void cancel() { } }
写完接口与实现之后来比较一下无状态会话bean与有状态会话bean,主要存在下面两方面的不同。
第一个区别是该bean类具有类似于普通Java类中属性的状态字段(这里指item),可以通过bean的业务方法对其进行修改。因为使用该bean的客户端可以有效的访问一个私有会话bean实例,并对其进行更改,注意这里的访问不是指客户端直接拿到某个bean的实例,而是通过业务接口对其进行操作。
第二个区别是拥有使用@Remove注解标记的方法。客户端将使用这些方法来结束与bean的会话。调用了一个这样的方法之后,服务器将销毁bean实例。如果后续尝试继续调用业务方法,那么客户端引用将会抛出异常(这与无状态会话bean不同,无状态会话bean更自由一些,拿来就用用完拉倒。这也是业界更倾向于不使用有状态会话bean的主要原因之一)。每个有状态会话bean必须至少定义一个使用@Remove注解标记的方法,即使该方法除了结束会话之外不做其他事情。在代码中如果用户完成了商店交易,就调用checkout()方法;相反,如果用户决定不继续交易就调用cancel()方法。两种情况都将会删除会话bean
2.生命周期回调
和无状态会话bean一样,有状态会话bean也支持生命周期的回调,以便于bean的初始化和清理。它还支持两个额外的回调,以允许bean有效地处理bean实例的钝化(passivation)和激活(activation)进程。钝化是由服务器序列化bean实例的进程,使得可以对它进行脱机存储以释放资源或者复制到集群中的另一个服务器上。激活处理则反序列化一个钝化的会话bean实例,使之在服务器上再次变得活动。因为有状态会话bean保留代表客户端的状态,并且直至调用bean的一个remove方法之后才会删除,所以,服务器不能销毁一个bean实例以释放资源。钝化允许在服务器保留会话状态的同时回收资源。
在钝化之前,服务器将调用Prepassivate回调。Bean使用此回调为bean的序列化做准备,通常关闭任何只想其他服务器资源的活动连接。PrePassivate方法由@prePassivate标记注解所标识。在已经激活bean之后,服务器将调用PostActivate回调。锁着序列化实例的恢复,bean必须重新获取bean的业务方法可能依赖的任何其他资源的连接。PostActivate方法由@PostActivate标记注解所标识。代码中显示了会话bean充分利用回调声明周期来维护一个JDBC连接。注意,只有JDBC连接是显式地管理。作为资源连接工厂,服务器在钝化和激活期间会自动保存和恢复数据源。
@Stateful // WARMING: // Resource declaration is covered later in the chapter. // use of mappedName is vendor specific. In this case, it is used // to specify the JNDI location of the datasource to use. @Resource(name="jdbc/ds", type=DataSource.class, mappedName="jdbc/sfsbLifecycleExample") public class OrderBrowserBean implements OrderBrowser { DataSource ds; Connection conn; @PostConstruct public void init() { // acquire the data source try { ds = (DataSource) new InitialContext().lookup("java:comp/env/jdbc/ds"); } catch (Exception e) { throw new EJBException(e); } acquireConnection(); } @PrePassivate public void passivate() { releaseConnection(); } @PostActivate public void activate() { acquireConnection(); } @PreDestroy public void shutdown() { releaseConnection(); } private void acquireConnection() { try { conn = ds.getConnection(); } catch (SQLException e) { throw new EJBException(e); } } private void releaseConnection() { try { conn.close(); } catch (SQLException e) { } conn = null; } public Collection<Order> listOrders() { // ... return new ArrayList<Order>(); } }