SeamFramework.orgCommunity Documentation

第 9 章 事件

9.1. 事件观察者
9.2. 事件生产者
9.3. 动态注册观察者
9.4. 事件绑定成员
9.5. 多个事件绑定
9.6. 事务性的观察者

Web Beans事件通知机制能够让Web Beans以完全解耦的方式交互。事件产生者(producers)触发事件,然后由Web Bean管理器发送给事件 观察者(observers) 。基本的模式和我们常见的观察者设计模式类似,但也有一些曲解:

一个 观察者方法是一个在Web Bean中的方法,该方法有一个用@Observes注释的参数。

public void onAnyDocumentEvent(@Observes Document document) { ... }

这个使用注释的参数被称为事件参数。事件参数的类型是被观察的事件类型。观察者方法也可以指定"选择器",选择器其实就是Web Beans绑定类型的实例。当一个绑定类型作为事件选择器时,它也被称为一个事件绑定类型

@BindingType

@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Updated { ... }

我们通过注释事件参数来指定观察者方法的事件绑定。

public void afterDocumentUpdate(@Observes @Updated Document document) { ... }

一个观察者方法无须指定一个事件绑定 — 在这种情况下,它对一个特定类型的 所有 事件都感兴趣(观察)。如果它没有指定事件绑定,那么它只对那些有事件绑定的事件感兴趣(观察)。

观察者方法可以有其他的参数,它们可以按照普通的Web Beans方法参数注入语法注入:

public void afterDocumentUpdate(@Observes @Updated Document document, User user) { ... }

事件生产者可以通过注入获得一个 事件通知者对象:

@Observable Event<Document

> documentEvent

@Observable注释隐式指定了一个 @Dependent 范围的,部署类型为@Standard 的Web Bean。其实现由Web Bean管理器提供。

一个生产者通过调用 Event 接口的 fire() 方法来触发事件,传递一个 事件对象

documentEvent.fire(document);

一个事件对象可以是没有任何类型变量或者通配类型参数的任何Java类的实例。事件可以通过符合下面条件的观察者方法传送:

Web Bean管理器简单地调用所有观察者方法,以事件参数值来传递事件对象。如果任何观察者方法抛出一个异常,Web Bean管理器将停止调用观察者方法,fire() 方法再次抛出这个异常。

要指定"选择器",事件生产者可以将一个事件绑定类型的实例传递给 fire() 方法:

documentEvent.fire( document, new AnnotationLiteral<Updated

>(){} );

AnnotationLiteral 帮助类能够让我们内部实例化绑定类型,这在Java里很难这么做。

事件将被发送给每个符合下面条件的观察者方法:

或者,通过注释事件通知者注入点来指定事件绑定:

@Observable @Updated Event<Document

> documentUpdatedEvent

然后,每个通过 Event 实例触发的事件都有注释的事件绑定。事件将被发送给每个符合下面条件的观察者方法:

动态地注册一个事件观察者相当有用。应用可以实现 Observer 接口,通过调用 observe() 方法注册一个事件通知者的观察者实例。

documentEvent.observe( new Observer<Document

>() { public void notify(Document doc) { ... } } );

事件绑定类型可以通过事件通知者注入点指定,或者将一个事件绑定类型实例传递给 observe() 方法:

documentEvent.observe( new Observer<Document

>() { public void notify(Document doc) { ... } }, 
                                                new AnnotationLiteral<Updated
>(){} );

一个事件绑定类型可以有注释成员:

@BindingType

@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Role {
    RoleType value();
}

成员值用来限制发送给观察者的消息:

public void adminLoggedIn(@Observes @Role(ADMIN) LoggedIn event) { ... }

事件绑定类型成员可以通过事件产生者静态地指定,通过在事件通知者注入点使用注释:

@Observable @Role(ADMIN) Event<LoggedIn

> LoggedInEvent;}}

或者事件绑定类型成员的值也可以由事件产生者动态地决定。我们先写一个 AnnotationLiteral 的抽象子类:

abstract class RoleBinding 

    extends AnnotationLiteral<Role
> 
    implements Role {}

事件产生者将一个这个类的实例传递给 fire()

documentEvent.fire( document, new RoleBinding() { public void value() { return user.getRole(); } } );

事件绑定类型可以组合,例如:

@Observable @Blog Event<Document

> blogEvent;
...
if (document.isBlog()) blogEvent.fire(document, new AnnotationLiteral<Updated
>(){});

当这个事件发生时,下列所有观察者方法都会被通知:

public void afterBlogUpdate(@Observes @Updated @Blog Document document) { ... }
public void afterDocumentUpdate(@Observes @Updated Document document) { ... }
public void onAnyBlogEvent(@Observes @Blog Document document) { ... }
public void onAnyDocumentEvent(@Observes Document document) { ... }}}

事务性的观察者在事件触发的事务完成阶段之前或者之后接收到事件通知。例如下面的观察者方法需要刷新在应用上下文中缓存的查询结果集,但只有在事务成果地更新了 Category 树之后才能发生:

public void refreshCategoryTree(@AfterTransactionSuccess @Observes CategoryUpdateEvent event) { ... }

我们有三种事务性观察者:

事务性的观察者在诸如Web Beans这类具有状态的对象模型中非常重要,因为状态通常比一个单一的原子事务所保留的时间更长。

设想一下我们已经在应用范围内缓存了一个JPA的查询结果集:

@ApplicationScoped @Singleton

public class Catalog {
    @PersistenceContext EntityManager em;
    
    List<Product
> products;
    @Produces @Catalog 
    List<Product
> getCatalog() {
        if (products==null) {
            products = em.createQuery("select p from Product p where p.deleted = false")
                .getResultList();
        }
        return products;
    }
    
}

一个 Product时常会被创建或者删除。当这个动作发生后,我们需要刷新 Product 分类。但是我们需要在事务完全成功之后才能处理刷新。

创建或者删除 Product 的Web Bean应该触发事件,例如:

@Stateless

public class ProductManager {
    @PersistenceContext EntityManager em;
    @Observable Event<Product
> productEvent;
    public void delete(Product product) {
        em.delete(product);
        productEvent.fire(product, new AnnotationLiteral<Deleted
>(){});
    }
    
    public void persist(Product product) {
        em.persist(product);
        productEvent.fire(product, new AnnotationLiteral<Created
>(){});
    }
    
    ...
    
}

现在 Catalog 能够观察到事务成功完成之后触发的事件:

@ApplicationScoped @Singleton

public class Catalog {
    ...
    
    void addProduct(@AfterTransactionSuccess @Observes @Created Product product) {
        products.add(product);
    }
    
    void addProduct(@AfterTransactionSuccess @Observes @Deleted Product product) {
        products.remove(product);
    }
    
}