SeamFramework.orgCommunity Documentation

章 9. 事件(Events)

9.1. 事件觀察器(Event observers)
9.2. 事件產生器(Event producers)
9.3. 動態式地註冊觀察器
9.4. member 的事件綁定
9.5. 多重事件綁定(Multiple event binding)
9.6. 交易觀察器(Transactional observers)

Web Bean 事件通知功能能讓 Web Bean 以一個完全 decouple 的方式來進行互動。事件產生器(producers)會產生事件並且之後會被透過 Web Bean 管理員來傳送給事件觀察器(observers)。這個基本的 schema 可能看起來和熟悉的觀察器/可觀察的格式類似,不過卻有幾點不大相同:

observer method 是個 Web Bean 的 method 並有個標記為 @Observes 的參數。

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

這個被標記的參數稱為事件參數(event parameter)。這個事件參數的 type 相當於被觀察到的事件類型(event type)。Observer method 也能指定「selector」,它們只不過是 Web Bean 綁定類型(binding types)的 instance。當有個綁定類型被用來作為一個事件選擇器時,它就會被稱為是一個事件綁定類型(event binding type)

@BindingType

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

我們可藉由標記事件參數來指定 observer method 的事件綁定:

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

一個 observer method 不需要指定任何的事件綁定 — 在此情況下它會專注於特定 type 的所有事件。若它指定了事件綁定的話,它就只會專注於也具有這些事件綁定的事件。

Observer method 能夠含有額外的參數,這些參數會被根據一般的 Web Bean method 參數注入語意(parameter injection semantic)來注入:

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

事件產生器可透過注入來取得一個事件通知器(event notifier) 物件:

@Observable Event<Document

> documentEvent

@Observable 這個標記暗示性地以 @Dependent 這個 scope 和 @Standard 這個建置類型,以及 Web Bean 管理員所提供的實做來定義了一個 Web Bean。

產生器可藉由調用 Event 介面的 fire() method,並傳送一個 event object 來產生事件:

documentEvent.fire(document);

Event object 可能會是個沒有 type variable 或是 wildcard type 參數的任何 Java class 的 instance。該事件會被傳送給符合下列條件的每個 observer method:

Web Bean 管理員會調用所有 observer method,並將 event object 作為是事件參數的值一般地來傳送。若任何 observer method 回傳了一個 exception,Web Bean 管理員便會停止調用 observer method,然後這個 exception 會被 fire() method 重新回傳。

若要指定一個「selector」,事件產生器可將事件綁定類型的一個 instance 傳送至 fire() 這個 method:

documentEvent.fire( document, new AnnotationLiteral<Updated

>(){} );

AnnotationLiteral 這個 helper class 可例示內部的綁定類型,因為要在 Java 中這麼作相當的困難。

事件會被傳送給符合下列條件的每個觀察器 method:

另外,事件綁定亦可透過標記事件通知器注入點來指定:

@Observable @Updated Event<Document

> documentUpdatedEvent

若是如此,所有透過 Event 的 instance 來產生的事件都會含有被標記的事件綁定。事件會被傳送給符合下列條件的每個觀察器 method:

通常以動態式的方式來註冊事件觀察器相當地有幫助。應用程式可實做 Observer 介面然後藉由調用 observe() method 來以一個事件通知器去註冊某個 instance。

documentEvent.observe( new Observer<Document

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

事件綁定類型能由事件通知器注入點來指定,或是透過將事件綁定類型的 instance 傳送至 observe() method:

documentEvent.observe( new Observer<Document

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

事件綁定類型能夠有下列標記成員:

@BindingType

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

member value(這是個編譯期常數)會被使用來過濾並減少傳送至觀察器的訊息數量:

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

事件綁定類型的成員可由事件產生器在事件通知器的注入點透過標記來靜態地指定:

@Observable @Role(ADMIN) Event<LoggedIn

> LoggedInEvent;}}

另外,事件綁定類型成員的值亦可藉由事件產生器來動態式地判斷出。我們首先先從編寫一個 AnnotationLiteral 的 abstract subclass 開始:

abstract class RoleBinding 

    extends AnnotationLiteral<Role
> 
    implements Role {}

事件產生器會將這個 class 的一個 instance 傳送至 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
>(){});

當這個事件發生時,下列所有 observer method 都會被通知:

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) { ... }}}

交易觀察器會在事件被產生後,於交易完成之前或之後收到它們的事件通知。比方說,下列 observer method 需要更新一組快取儲存於應用程式 context 中的查詢結果,不過只有更新了 Category tree 的交易會成功:

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

交易觀察器分為三種類型:

交易觀察器在一個像是 Web Bean 的 stateful 物件模型中相當地重要,因為 state 一般被保持的時間比單一 atomic transaction(不可分割的交易)還要長。

想像我們已快取了一個設置於應用程式 scope 中的 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 的 catalog。不過我們還是應該等到交易成功完成之後才去進行這項更新!

建立和刪除 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);
    }
    
}