CDI(Weld)高級<4> Event(事件)

之前發過一個粗略篇,已經刪除.此次從新修訂. html

Cdi中的event事件,是整個CDI的精華所在之一.其有點相似設計模式中的觀察者模式.但也有不一樣的地方.以下3: java

  1. 不只是生產者(producers)從觀察者(observers)解耦.觀察者也從生產者解耦.
  2. 觀察者能夠指定「選擇器」的組合來縮小的事件通知
  3. 觀察者能夠當即通知,或者能夠指定交付的事件應該推遲到當前事務的結束。

即用一種維護生產者觀察者之間的分離代碼的方式,來產生和訂閱(即觀察)在應用程序中發生的事件。使用 javax.enterprise.event.Event 類建立事件,並使用 CDI 的 @Observes 標註訂閱處理事件。 設計模式

1. Event payload(事件的有效載入)

事件對象只不過是一個具體的Java類的實例。
一個事件可指定限定符,觀察者能夠區別於其餘相同類型的事件。
限定符的功能很像主題選擇器, 容許限定符決定觀察器將觀察哪些事件。
使用@ qualifier定義的一個例子: 緩存

@Qualifier
@Target({METHOD, FIELD, PARAMETER, TYPE})
@Retention(RUNTIME)
public @interface Updated {}
另外,事件的建立和訂閱是類型安全的.

2. Event observers(event的觀察者)

一個觀察者的處理方式是在方法中,加入一個參數註解@Observes.以下所示: 安全

public void onAnyDocumentEvent(@Observes Document document) 
{ ... }
帶註解的參數稱爲事件參數。事件的參數類型是觀察到的事件類型。事件參數還能夠指定限定符。以下:
public void afterDocumentUpdate(@Observes @Updated Document document) { ... }
固然也能夠有其餘參數
public void afterDocumentUpdate(@Observes @Updated Document document, User user) { ... }

3. Event producers(event生產者)

Event producers的fire事件是使用參數化Event interface的實例.以下,經過@Inject注入該接口的一個實例. app

@Inject @Any Event<Document> documentEvent;
而事件生產者經過調用fire()方法,並傳遞"事件對象"從而激活事件處理.
documentEvent.fire(document);
經過事件對象的參數值,容器調用全部觀察者的方法,若是任何觀察者方法拋出一個異常,容器會中止調用觀察者方法,異常將會由fire()方法拋出。
Qualifiers 在事件中應用方式有兩種:

註解注入的缺點是,咱們不能動態地指定限定符。
CDI也考慮到了這一點. less

4.AnnotationLiteral動態注入對應事件

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()方法可使用任意的註解組合在事件注入點和限定符實例上. 學習

5.Conditional observer methods

默認狀況下,在當前上下文若是沒有一個觀察者的實例,容器將爲事件實例化觀察者.
但咱們但願傳遞給觀察者的實例是已經存在於上下文中的觀察者.
指定一個有條件的觀察者的方式是在@Observes註釋上添加receive = IF_EXISTS ui

public void refreshOnDocumentUpdate(@Observes(receive = IF_EXISTS) @Updated Document d) { ... }
Note
A bean with scope @Dependent cannot be a conditional observer, since it would never be called!

6.Event qualifiers with members

@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方式中的使用:
先定義一個AnnotationLiteral的抽象類
abstract class RoleBinding extends AnnotationLiteral<Role> implements Role {}
經過select()方法的使用代碼
documentEvent.select(
    new RoleBinding() {public void value() { return user.getRole(); }}
    ).fire(document);

7.Multiple event qualifiers

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並未包含在事件發生處.

8.事務性處理的transactional observers

事務處理的observers 在事務完成以前或以後的階段纔會收到事件通知.
例如,下面的觀察方法須要在應用程序上下文中刷新一個查詢的結果集,可是隻有在 Category 更新成功纔會執行:

public void refreshCategoryTree(@Observes(during = AFTER_SUCCESS) CategoryUpdateEvent event) { ... }
一共有五種transactional observers:
  1. IN_PROGRESS       --- observers被當即通知  (default)
  2. AFTER_SUCCESS     --- 在事務成功完成後,observers會被通知.
  3. AFTER_FAILURE     --- 在事務完成失敗後,observers會被通知.
  4. AFTER_COMPLETION  --- observers在交易完成後的階段被調用
  5. BEFORE_COMPLETION --- 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);
   }
}

DEMO

概述流程:

我在這裏也是實際闡釋一下.畢竟國內CDI方面的東西基本沒有,也給學習CDI的朋友一個參考.

A: event 主體

首先是2個事件.  1.run,跑 事件   2.walk,走 事件
頁面觸發這2個事件.首先在後臺定義@Qualifier,對應每一個事件. 在CDI中全部的對象和生產者都是限定類型的.因此須要指定具體的@Qualifier.而這裏事件就2個,因此以下:

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.

B: Ob 定義

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的方法.就會去執行這個方法.\

C: producer 定義

這裏就要繼續寫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方法!

相關文章
相關標籤/搜索