/这是描述 Web Beans 规范当前状态的系列文章的第五部分。请先阅读 第一部分、第二部分、第三部分 和 第四部分./
Web Beans 的一个主题是 松耦合。我们已看到了三种实现松耦合的方法
- 组件类型 允许 /部署时/ 多态性
- 生产者方法 允许 /运行时/ 多态性
- 上下文生命周期管理 解耦组件生命周期
这些技术有助于实现客户端和服务器之间的松耦合。客户端不再紧密绑定到 API 的实现,也不需要管理服务器对象的生命周期。这种方法让 /有状态对象能够像服务一样交互/。
Web Beans 提供了两个额外的、重要的设施,进一步推动了松耦合的目标
- 拦截器 解耦技术关注点与业务逻辑
- 事件通知 解耦事件生产者与事件消费者
让我们来探讨这些特性。
拦截器
Web Beans 重新使用 EJB 3.0 的基本拦截器架构,并在两个方向上扩展了功能
- 任何 Web Bean 都可以有拦截器,而不仅仅是会话 Bean
- Web Beans 采用了一种更复杂的基于注解的方法来将拦截器绑定到组件上
假设我们想要声明我们的某些组件是事务性的。首先,我们需要一个 /拦截器绑定注解/ 来指定我们感兴趣的确切组件
@InterceptorBindingType @Target({METHOD, TYPE}) @Retention(RUNTIME) public @interface Transactional {}
现在我们可以轻松地指定我们的ShoppingCart是一个事务性对象
@Transactional @Component public class ShoppingCart { ... }
或者,如果我们更喜欢,我们也可以指定仅有一个方法的事务性
@Component public class ShoppingCart { @Transactional public void checkout() { ... } }
那很好,但沿着这条线,我们最终需要实际实现提供这种事务管理方面的拦截器。我们只需要创建一个标准的EJB拦截器,并对其进行注释@Interceptor和@Transactional.
@Transactional @Interceptor public class TransactionInterceptor { @AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... } }
最后,我们需要在web-beans.xml中/启用/我们的拦截器.
<interceptors> <interceptor>to.relation.in.TransactionInterceptor</interceptor> </interceptors>
哇!为什么是角标粥?
嗯,XML声明解决了两个问题
- 它使我们能够指定系统中所有拦截器的总排序,确保确定性行为
- 它允许我们在部署时启用或禁用拦截器类
例如,我们可以指定我们的安全拦截器在TransactionInterceptor之前运行。我们还可以在我们的测试环境中关闭它们。
具有成员的拦截器绑定
假设我们想在我们的@Transactional注解中添加一些额外信息
@InterceptorBindingType @Target({METHOD, TYPE}) @Retention(RUNTIME) public @interface Transactional { boolean requiresNew() default false; }
Web Bean将使用requiresNew的值来在两个不同的拦截器之间进行选择,TransactionInterceptor和RequiresNewTransactionInterceptor.
@Transactional(requiresNew=true) @Interceptor public class RequiresNewTransactionInterceptor { @AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... } }
现在我们可以这样使用RequiresNewTransactionInterceptor但是,如果我们只有一个拦截器,并且我们想让容器忽略绑定拦截器时的
@Transactional(requiresNew=true) @Component public class ShoppingCart { ... }
值,我们可以使用requiresNew@NonBinding多个拦截器绑定注释注解中添加一些额外信息
@InterceptorBindingType @Target({METHOD, TYPE}) @Retention(RUNTIME) public @interface Secure { @NonBinding String[] rolesAllowed() default {}; }
通常,我们使用拦截器绑定类型的组合来将多个拦截器绑定到一个组件上。例如,以下声明将被用于将
SecurityInterceptorTransactionInterceptor和绑定到同一组件然而,在非常复杂的情况下,拦截器本身可能指定一些拦截器绑定类型的组合
@Secure(rolesAllowed="admin") @Transactional @Component public class ShoppingCart { ... }
然后这个拦截器可以使用以下任一组合绑定到
@Transactional @Secure @Interceptor public class TransactionalSecureInterceptor { ... }
checkout()方法将拦截器绑定到/一切/
@Component public class ShoppingCart { @Transactional @Secure public void checkout() { ... } }
@Secure @Component public class ShoppingCart { @Transactional public void checkout() { ... } }
@Transactionl @Component public class ShoppingCart { @Secure public void checkout() { ... } }
@Transactional @Secure @Component public class ShoppingCart { public void checkout() { ... } }
如果我们想为/每个/组件提供一个拦截器怎么办?很简单,只需声明没有拦截器绑定类型的拦截器即可!
拦截器绑定类型继承
@Interceptor public class UberInterceptor { ... }
Java语言的一个糟糕、尴尬的错误是不支持注释继承。实际上,注释应该具有内置的重用,以便这种事情可以工作
嗯,幸运的是,Web Beans绕过了Java的这个缺失特性
public @interface Action extends Transactional, Secure { ... }
现在,任何被
@Transactional @Secure @InterceptorBindingType @Target(TYPE) @Retention(RUNTIME) public @interface Action { ... }
@Action注释的组件都将绑定到。 (甚至TransactionInterceptor和绑定到同一组件TransactionalSecureInterceptor,如果它存在的话。)事件
/请注意,以下部分描述的功能仍在Web Beans专家组中积极讨论!/
Web Beans事件通知设施允许组件以完全解耦的方式交互。事件/生产者/引发事件,然后事件被传递给事件/观察者/。这个基本模式可能听起来与观察者/可观察模式相似,但有几个变化
不仅事件生产者与观察者解耦;观察者与生产者也完全解耦
- 观察者可以指定一系列
选择器
来缩小他们将接收的事件通知的集合 - 观察者可以立即通知,也可以指定事件应在当前事务结束时延迟发送
- 事件观察者
一个/观察者方法/是任何Web Bean的方法,该方法有一个注解
@Observes的参数.
public void onAnyDocumentEvent(@Observes Document document) { ... }
被注解的参数称为/事件参数/。观察者方法还可以指定选择器
,称为/事件绑定类型/。事件绑定类型只是一个注释
@EventBindingType @Target({PARAMETER, FIELD}) @Retention(RUNTIME) public @interface Updated { ... }
我们通过注释事件参数来指定选择器
public void afterDocumentUpdate(@Observes @Updated Document document) { ... }
观察者方法可以具有额外的参数,这些参数将根据Web Beans的语义进行注入
public void afterDocumentUpdate(@Observes @Updated Document document, @Current User user) { ... }
事件生产者
事件生产者可以通过注入获得/事件通知器/
@In @Notifier Event<Document> documentEvent;
The@Notifier注释隐式定义了一个具有作用域@Dependent和组件类型@Standard.
生产者通过调用Event接口
documentEvent.raise(document);
的唯一方法来引发事件。要指定一个:
documentEvent.raise(document, new Updated(){});
选择器
,生产者可以将事件绑定类型的实例传递给
@In @Notifier @Updated Event<Document> documentUpdatedEvent;
raise()
方法,或者可以在注入点指定选择器。
@EventBindingType @Target({PARAMETER, FIELD}) @Retention(RUNTIME) public @interface Role { RoleType value(); }
具有成员的事件绑定
public void adminLoggedIn(@Observes @Role(ADMIN) LoggedIn event) { ... }
事件绑定类型可能具有注解成员。
@In @Notifier @Role(ADMIN) Event<LoggedIn> LoggedInEvent;
成员值用于缩小发送给观察者的消息
documentEvent.raise( document, new Role() { public void value() { return user.getRole(); } } );
并且可以由事件生产者静态或动态指定。
多个事件绑定
@In @Notifier @Blog Event<Document> blogEvent; ... if (document.isBlog()) blogEvent.raise(document, new Updated(){});
事件绑定类型可以组合,例如
public void afterBlogUpdate(@Observes @Updated @Blog Document document) { ... }
public void afterDocumentUpdate(@Observes @Updated Document document) { ... }
public void onAnyBlogEvent(@Observes @Bog Document document) { ... }
public void onAnyDocumentEvent(@Observes Document document) { ... }
在这种情况下,以下任何观察者方法都将被通知
事务性观察者事务性观察者在引发事件的交易的前或后完成阶段接收事件通知。例如,以下观察者方法需要刷新应用上下文中缓存的查询结果集,但仅当更新分类
public void refreshCategoryTree(@AfterTransactionSuccess @Observes CategoryUpdateEvent event) { ... }
树的交易成功时。
- 有三种类型的事务性观察者@AfterTransactionSuccess
- 观察者在交易后完成阶段被调用,但只有当交易成功完成时。@AfterTransactionCompletion
- 观察者在交易后完成阶段被调用。@BeforeTransactionCompletion
观察者在交易前完成阶段被调用。