SeamFramework.orgCommunity Documentation
目前为止,我们已经看到了几个 范围类型注释的例子。Web Bean的范围决定了Web Bean实例的生命周期。范围还决定了哪个客户端引用了哪个Web Bean实例。根据Web Beans规范,一个范围决定:
该范围的Web Bean的一个实例何时被创建
该范围的Web Bean实例何时被销毁
注入的引用指向该范围的Web Bean的哪个实例
例如,如果我们有一个会话范围的Web Bean:CurrentUser
。那么在同一个HttpSession
的上下文中调用的所有的Web Bean都将看到同一个CurrentUser
实例。这个实例在会话第一次需要CurrentUser
时被自动创建,在会话结束时被自动销毁。
Web Bean有一个特性是可扩展的上下文模型。我们可以创建一个新的范围类型注释来定一个新的范围:
@Retention(RUNTIME)
@Target({TYPE, METHOD})
@ScopeType
public @interface ClusterScoped {}
当然,这是这项工作最简单的部分。为了让这个范围类型可以使用,我们还需要定义一个Context(上下文)
对象来实现这个范围!实现上下文通常是一个非常具备挑战的技术任务,这常常只能由开发编程框架的专家完成。
我们可以在Web Bean实现类中应用范围类型注释来指定Web Bean的范围:
@ClusterScoped
public class SecondLevelCache { ... }
通常,你将会使用一个Web Bean内置的范围。
Web Beans定义了四个内置范围:
@RequestScoped
@SessionScoped
@ApplicationScoped
@ConversationScoped
对于使用Web Beans的Web应用:
任何Servlet请求可以访问激活的请求,会话和应用范围,并且
任何JSF请求可以访问一个激活的对话范围
在下列情况下请求和应用范围是激活的:
在EJB远程方法调用期间,
在EJB超时期间,
在消息发送给消息驱动Bean的期间,
在Web Service调用期间。
如果应用试图调用一个Web Bean,而对应的范围上下文没有处于激活状态时,Web Bean管理器在运行时将抛出一个ContextNotActiveException
异常。
这四个内置范围的其中三个对于每个Java EE程序员来说都非常熟悉,所以让我们别浪费时间来讨论他们。不过有一个范围是新的。
Web Beans的对话(Conversation)范围有点类似与传统的会话范围(Session),传统的会话范围常常用来存储和系统用户相关的状态,并且能够跨越多个请求。然而,对话范围还有很多地方和会话范围不一样:
它通过应用显式地声明,并且
在一个JSF应用中持有与一个特定的Web浏览标签页关联的状态。
从用户角度出发,一个对话代表一个任务,或者一个工作单元。用户当前工作相关的状态由对话上下文维护。如果用户同时处理多个事情,就会有多个对话与之对应。
一个对话上下文在任何JSF请求中都是激活的。但是,大部分对话都在请求结束的时候被销毁了。如果一个对话需要跨越多个请求来维护状态的话,它必须显式地升级为长时对话。
Web Beans提供了一个内置的Web Bean来在控制一个JSF应用中对话的生命周期。这个Web Bean可以通过注入来获得:
@Current Conversation conversation;
将当前请求关联的对话升级为长时对话的方法是从应用代码中调用 begin()
方法。将当前长时对话上下文在当前请求结束时销毁的方法是调用end()
方法。
在下面的例子中,一个对话范围的Web Bean控制和它关联的对话:
@ConversationScoped @Stateful
public class OrderBuilder {
private Order order;
private @Current Conversation conversation;
private @PersistenceContext(type=EXTENDED) EntityManager em;
@Produces public Order getOrder() {
return order;
}
public Order createOrder() {
order = new Order();
conversation.begin();
return order;
}
public void addLineItem(Product product, int quantity) {
order.add( new LineItem(product, quantity) );
}
public void saveOrder(Order order) {
em.persist(order);
conversation.end();
}
@Remove
public void destroy() {}
}
Web Bean可以通过Conversation
接口控制自己的生命周期。但是其他一些Web Bean的生命周期完全依赖与其他对象。
对话上下文在任何JSF faces请求中自动传播(JSF表单提交)。在非faces请求中,对话上下文将不会自动传播,例如通过一个链接来导航。
我们可以强迫在非faces请求中传播对话,方法是在请求参数中包含一个对话的唯一标识符即可。Web Beans规范为此保留了一个请求参数关键字cid
。对话的唯一标识符可以通过Conversation
对象获得,它拥有Web Beans的名字conversation
。
因此,下面的链接能够传播对话:
<a href="/addProduct.jsp?cid=#{conversation.id}" >Add Product</a >
Web Bean管理器也需要能够跨越任何重定向来传播对话,甚至这个对话没有被升级为长时对话。这样我们就能很容易实现常用的POST-then-redirect模式,而不需要构建一个脆弱的 "闪存"对象。在这个例子中,Web Bean管理器自动向重定向URL中添加一个请求参数。
除了内置的四个范围,Web Beans还提供了一个依赖的伪范围。这个范围是没有显式设置范围类型的Web Bean的默认范围。
例如,这个Web Bean有一个范围类型@Dependent
:
public class Calculator { ... }
当一个注入点被解析为一个依赖的Web Bean之后,每当第一个Web Bean被初始化时,都会创建一个依赖的Web Bean实例。不同的Web Beans或者不同的注入点的依赖的Web Beans的实例都不会被共享。它们是其它Web Bean实例的依赖的对象。
依赖的Web Bean实例在它们所依赖对象实例销毁的时候被销毁。
Web Bean能够让我们轻松获得一个Java类或者EJB Bean的依赖实例,甚至这个类或者EJB Bean已经被声明为一个其他范围的Web Bean也没问题。
内置的@New
绑定注释允许在注入点隐式地定义一个依赖的Web Bean。假设我们需要声明下面的注入域:
@New Calculator calculator;
这个Web Bean被隐式地定义为范围为@Dependent
,绑定类型为@New
,API类型为Calculator
,实现了Calculator
类,部署类型为@Standard
。
甚至在Calculator
已经通过不同的范围类型声明过的情况下也是如此。例如:
@ConversationScoped
public class Calculator { ... }
所以下面注入的属性,每个都获得一个不同的 Calculator
实例:
public class PaymentCalc {
@Current Calculator calculator;
@New Calculator newCalculator;
}
calculator
域有一个对话范围的Calculator
实例注入。newCalculator
域有一个新的Calculator
实例注入,这个实例的生命周期绑定在其拥有者PaymentCalc
类上。
这个特性对于生产者方法来说特别有用,我们将在下一章看到。