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

 

以前发过一个粗略篇,已经删除.这次重新修订.

Cdi中的event事件,是整个CDI的精华所在之一.其有点类似设计模式中的观察者模式.但也有不同的地方.如下3:

  1. 不仅是生产者(producers)从观察者(observers)解耦.观察者也从生产者解耦.
  2. 观察者可以指定“选择器”的组合来缩小的事件通知
  3. 观察者可以立即通知,或者可以指定交付的事件应该推迟到当前事务的结束。

即用一种维护生产者和观察者之间的分离代码的方式,来产生和订阅(即观察)在应用程序中发生的事件。使用 javax.enterprise.event.Event 类创建事件,并使用 CDI 的 @Observes 标注订阅处理事件。

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

事件对象只不过是一个具体的Java类的实例。
一个事件可指定限定符,观察者可以区别于其他相同类型的事件。
限定符的功能很像主题选择器, 允许限定符决定观察器将观察哪些事件。
使用@ qualifier定义的一个例子:

?
1
2
3
4
@Qualifier
@Target ({METHOD, FIELD, PARAMETER, TYPE})
@Retention (RUNTIME)
public @interface Updated {}

另外,事件的创建和订阅是类型安全的.

2. Event observers(event的观察者)

一个观察者的处理方式是在方法中,加入一个参数注解@Observes.如下所示:

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

带注解的参数称为事件参数。事件的参数类型是观察到的事件类型。事件参数还可以指定限定符。如下:

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

当然也可以有其他参数

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

3. Event producers(event生产者)

Event producers的fire事件是使用参数化Event interface的实例.如下,通过@Inject注入该接口的一个实例.

?
1
@Inject @Any Event<Document> documentEvent;

而事件生产者通过调用fire()方法,并传递"事件对象"从而激活事件处理.

?
1
documentEvent.fire(document);

通过事件对象的参数值,容器调用所有观察者的方法,如果任何观察者方法抛出一个异常,容器会停止调用观察者方法,异常将会由fire()方法抛出。
Qualifiers 在事件中应用方式有两种:

注解注入的缺点是,我们不能动态地指定限定符。
CDI也考虑到了这一点.

4.AnnotationLiteral动态注入对应事件

?
1
documentEvent.select( new AnnotationLiteral<Updated>(){}).fire(document);

documentEvent注入点不用再使用限定符 @Updated. 这样可以在程序中判断后进行分支处理.

?
1
2
3
4
5
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

?
1
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

?
1
2
3
4
5
6
7
@Qualifier
@Target ({METHOD, FIELD, PARAMETER, TYPE})
@Retention (RUNTIME)
public @interface Role {
 
    RoleType value();
}

可以通过注解的value值传递信息给observer.

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

在事件注入点的使用

?
1
@Inject @Role (ADMIN) Event<LoggedIn> loggedInEvent;

在AnnotationLiteral方式中的使用:
先定义一个AnnotationLiteral的抽象类

?
1
abstract class RoleBinding extends AnnotationLiteral<Role> implements Role {}

通过select()方法的使用代码

?
1
2
3
documentEvent.select(
     new RoleBinding() { public void value() { return user.getRole(); }}
     ).fire(document);

7.Multiple event qualifiers

qualifiers 是可以多重组合的.如下代码:

?
1
2
3
@Inject @Blog Event<Document> blogEvent;
...
if (document.isBlog()) blogEvent.select( new AnnotationLiteral<Updated>(){}).fire(document);

下面所有这些观察方法将得到通知。

?
1
2
3
4
5
6
7
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) { ... }}}

然而,如果还有一个观察者的方法:

?
1
public void afterPersonalBlogUpdate( @Observes @Updated @Personal @Blog Document document) { ... }

它不会通知,因为@Personal并未包含在事件发生处.

8.事务性处理的transactional observers

事务处理的observers 在事务完成之前或之后的阶段才会收到事件通知.
例如,下面的观察方法需要在应用程序上下文中刷新一个查询的结果集,但是只有在 Category 更新成功才会执行:

?
1
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查询,

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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可以引发事件,例如:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
    }
    ...
}

在事务完成后,对产品目录用观察者的方法进行更新/删除

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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的场景,

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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 定义

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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定义里的最后一段代码.

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<? 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代码:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@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方法!

 

原文:http://my.oschina.net/zhaoqian/blog/265207#OSC_h1_7

你可能感兴趣的:(event)