之前發過一個粗略篇,已經刪除.此次從新修訂. html
Cdi中的event事件,是整個CDI的精華所在之一.其有點相似設計模式中的觀察者模式.但也有不一樣的地方.以下3點: java
即用一種維護生產者和觀察者之間的分離代碼的方式,來產生和訂閱(即觀察)在應用程序中發生的事件。使用 javax.enterprise.event.Event 類建立事件,並使用 CDI 的 @Observes 標註訂閱處理事件。 設計模式
事件對象只不過是一個具體的Java類的實例。
一個事件可指定限定符,觀察者能夠區別於其餘相同類型的事件。
限定符的功能很像主題選擇器, 容許限定符決定觀察器將觀察哪些事件。
使用@ qualifier定義的一個例子: 緩存
@Qualifier @Target({METHOD, FIELD, PARAMETER, TYPE}) @Retention(RUNTIME) public @interface Updated {}另外,事件的建立和訂閱是類型安全的.
一個觀察者的處理方式是在方法中,加入一個參數註解@Observes.以下所示: 安全
public void onAnyDocumentEvent(@Observes Document document) { ... }帶註解的參數稱爲事件參數。事件的參數類型是觀察到的事件類型。事件參數還能夠指定限定符。以下:
public void afterDocumentUpdate(@Observes @Updated Document document) { ... }固然也能夠有其餘參數
public void afterDocumentUpdate(@Observes @Updated Document document, User user) { ... }
Event producers的fire事件是使用參數化Event interface的實例.以下,經過@Inject注入該接口的一個實例. app
@Inject @Any Event<Document> documentEvent;而事件生產者經過調用fire()方法,並傳遞"事件對象"從而激活事件處理.
documentEvent.fire(document);經過事件對象的參數值,容器調用全部觀察者的方法,若是任何觀察者方法拋出一個異常,容器會中止調用觀察者方法,異常將會由fire()方法拋出。
@Inject @Updated Event<Document> documentUpdatedEvent;
public void afterDocumentUpdate(@Observes @Updated Document document) { ... }
註解注入的缺點是,咱們不能動態地指定限定符。
CDI也考慮到了這一點. less
documentEvent.select(new AnnotationLiteral<Updated>(){}).fire(document);
documentEvent注入點不用再使用限定符 @Updated. 這樣能夠在程序中判斷後進行分支處理. ide
if(num==1){ documentEvent.select(new AnnotationLiteral<Updated>(){}).fire(document); }else{ documentEvent.select(new AnnotationLiteral<Other>(){}).fire(document); }
事件能夠有多個事件限定符,經過select()方法可使用任意的註解組合在事件注入點和限定符實例上. 學習
默認狀況下,在當前上下文若是沒有一個觀察者的實例,容器將爲事件實例化觀察者.
但咱們但願傳遞給觀察者的實例是已經存在於上下文中的觀察者.
指定一個有條件的觀察者的方式是在@Observes註釋上添加receive = IF_EXISTS ui
public void refreshOnDocumentUpdate(@Observes(receive = IF_EXISTS) @Updated Document d) { ... }Note
@Qualifier @Target({METHOD, FIELD, PARAMETER, TYPE}) @Retention(RUNTIME) public @interface Role { RoleType value(); }能夠經過註解的value值傳遞信息給observer.
public void adminLoggedIn(@Observes @Role(ADMIN) LoggedIn event) { ... }在事件注入點的使用
@Inject @Role(ADMIN) Event<LoggedIn> loggedInEvent;在AnnotationLiteral方式中的使用:
abstract class RoleBinding extends AnnotationLiteral<Role> implements Role {}經過select()方法的使用代碼
documentEvent.select( new RoleBinding() {public void value() { return user.getRole(); }} ).fire(document);
qualifiers 是能夠多重組合的.以下代碼:
@Inject @Blog Event<Document> blogEvent; ... if (document.isBlog()) blogEvent.select(new AnnotationLiteral<Updated>(){}).fire(document);下面全部這些觀察方法將獲得通知。
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) { ... }}}然而,若是還有一個觀察者的方法:
public void afterPersonalBlogUpdate(@Observes @Updated @Personal @Blog Document document) { ... }它不會通知,由於@Personal並未包含在事件發生處.
事務處理的observers 在事務完成以前或以後的階段纔會收到事件通知.
例如,下面的觀察方法須要在應用程序上下文中刷新一個查詢的結果集,可是隻有在 Category 更新成功纔會執行:
public void refreshCategoryTree(@Observes(during = AFTER_SUCCESS) CategoryUpdateEvent event) { ... }一共有五種transactional observers:
在一個有狀態的對象模型(stateful object model)中,Transactional observers是很是重要的.由於那些狀態常常是長事務的.
想象一下,咱們已經在application scope範圍緩存一個JPA查詢,
import javax.ejb.Singleton; import javax.enterprise.inject.Produces; @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; } }
若是一個產品被建立或刪除,咱們須要從新整理產品目錄,這個時候咱們必需要等到這個更新的事務成功完成後.
建立和刪除產品的Bean能夠引起事件,例如:import javax.enterprise.event.Event; @Stateless public class ProductManager { @PersistenceContext EntityManager em; @Inject @Any Event<Product> productEvent; public void delete(Product product) { em.delete(product); productEvent.select(new AnnotationLiteral<Deleted>(){}).fire(product); } public void persist(Product product) { em.persist(product); productEvent.select(new AnnotationLiteral<Created>(){}).fire(product); } ... }
在事務完成後,對產品目錄用觀察者的方法進行更新/刪除
import javax.ejb.Singleton; @ApplicationScoped @Singleton public class Catalog { ... void addProduct(@Observes(during = AFTER_SUCCESS) @Created Product product) { products.add(product); } void removeProduct(@Observes(during = AFTER_SUCCESS) @Deleted Product product) { products.remove(product); } }
概述流程:
我在這裏也是實際闡釋一下.畢竟國內CDI方面的東西基本沒有,也給學習CDI的朋友一個參考.
run.java
import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; @Qualifier @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RUNTIME) @Documented public @interface Run { }walk.java
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Qualifier @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RUNTIME) @Documented public @interface Walk { }定義好後,咱們須要定義具體的事件處理的主題.也就是運動.不論是跑仍是走,都是運動的一種.因此定義運動事件主體.
其實主要是由於在這裏是本身想的一個CDI EVENT的場景,
import java.util.Date; public class ExerciseEvent { private String type; //walk or run private Long howfar; private Date datetime; public String getType() { return type; } public void setType(String type) { this.type = type; } public Long getHowfar() { return howfar; } public void setHowfar(Long howfar) { this.howfar = howfar; } public Date getDatetime() { return datetime; } public void setDatetime(Date datetime) { this.datetime = datetime; } @Override public String toString() { return "在"+this.datetime+",你"+this.type+"--"+(this.howfar.toString()); } }不忙處理頁面,這個時候,咱們應該對走仍是跑作具體的處理.
分析一下,cdi的event處理,主要是2個,一個ob一個producer.如今,咱們已經定義好了event.那麼接着就是先處理observer.
public class ExerciseHandler implements Serializable{ private static final long serialVersionUID = 3245934049396896828L; @Inject private Logger log; List<ExerciseEvent> exercise=new ArrayList<ExerciseEvent>(); public void run(@Observes @Run ExerciseEvent runEvent){ log.info("CDI---run方法!"); this.exercise.add(runEvent); } public void walk(@Observes @Walk ExerciseEvent walkEvent){ log.info("CDI---walk方法!"); this.exercise.add(walkEvent); } @Produces @Named public List<ExerciseEvent> getExercise() { return exercise; } }
相關語法說明,若是看了上面翻譯自jboss的文檔的說明外,應該也就明白這麼的代碼意思.
這個類的run方法將會在系統觀察到有地方觸發了限定符爲@run,而且事件是ExerciseEvent的方法.就會去執行這個方法.\
這裏就要繼續寫producer的相關類了.本例你們能夠知道,是由頁面觸發的相關Exercise事件.run or walk.先是一個頁面.
JSF頁面,你們能夠看到h:dateTable.在看看上面的 @Produces註解.就上面OB定義裏的最後一段代碼.
<?xml version="1.0" encoding="UTF-8"?> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" template="/WEB-INF/templates/template.xhtml"> <ui:define name="content"> <h:form> <h:outputLabel value="Far:" /> <h:inputText value="#{exerciseBean.far}" /> <h:selectOneRadio value="#{exerciseBean.type}" required="true"> <f:selectItem itemLabel="Run" itemValue="run" /> <f:selectItem itemLabel="Walk" itemValue="walk" /> </h:selectOneRadio> <h:commandButton value="Go!!!" action="#{exerciseBean.process()}" /> </h:form> <h:dataTable var="exercise" value="#{exercise}" styleClass="zebra-striped"> <h:column> <f:facet name="header">Date</f:facet> <h:outputText value="#{exercise.datetime}"> <f:convertDateTime type="date" pattern="yyyy/MM/dd" /> </h:outputText> </h:column> <h:column> <f:facet name="header">type</f:facet> <h:outputText value="#{exercise.type}" /> </h:column> <h:column> <f:facet name="header">howfar</f:facet> <h:outputText value="#{exercise.howfar}" /> </h:column> </h:dataTable> </ui:define> </ui:composition>對應的backingBean代碼:
@Named @SessionScoped public class ExerciseBean implements Serializable{ private static final long serialVersionUID = -2164098635097534027L; @Inject private Logger log; @Inject @Run Event<ExerciseEvent> runEventProducer; @Inject @Walk Event<ExerciseEvent> walkEventProducer; private String type="run"; private Long far; private ExerciseEvent event=new ExerciseEvent(); public void process(){ event.setType(this.type); event.setHowfar(far); event.setDatetime(new Date()); if(this.event.getType().equals("run")){ log.info("Run--Fire"); this.runEventProducer.fire(event); }else{ log.info("Walk--Fire"); this.walkEventProducer.fire(event); } } public ExerciseEvent getEvent() { return event; } public void setEvent(ExerciseEvent event) { this.event = event; } public Event<ExerciseEvent> getRunEventProducer() { return runEventProducer; } public void setRunEventProducer(Event<ExerciseEvent> runEventProducer) { this.runEventProducer = runEventProducer; } public Event<ExerciseEvent> getWalkEventProducer() { return walkEventProducer; } public void setWalkEventProducer(Event<ExerciseEvent> walkEventProducer) { this.walkEventProducer = walkEventProducer; } public String getType() { return type; } public void setType(String type) { this.type = type; } public Long getFar() { return far; } public void setFar(Long far) { this.far = far; } }啓動頁面後,點擊按鈕,選擇不一樣的 radio方式 ,run或者walk,我就不截圖了.
發一點後臺的輸出:
16:33:01,899 INFO (http-/0.0.0.0:8080-1) Walk--Fire 16:33:01,901 INFO (http-/0.0.0.0:8080-1) CDI---walk方法! 16:51:55,712 INFO (http-/0.0.0.0:8080-1) Walk--Fire 16:51:55,713 INFO (http-/0.0.0.0:8080-1) CDI---walk方法! 16:52:54,044 INFO (http-/0.0.0.0:8080-1) Run--Fire 16:52:54,045 INFO (http-/0.0.0.0:8080-1) CDI---run方法!