目录[-]
以前发过一个粗略篇,已经删除.这次重新修订.
Cdi中的event事件,是整个CDI的精华所在之一.其有点类似设计模式中的观察者模式.但也有不同的地方.如下3点:
即用一种维护生产者和观察者之间的分离代码的方式,来产生和订阅(即观察)在应用程序中发生的事件。使用 javax.enterprise.event.Event 类创建事件,并使用 CDI 的 @Observes 标注订阅处理事件。
事件对象只不过是一个具体的Java类的实例。
一个事件可指定限定符,观察者可以区别于其他相同类型的事件。
限定符的功能很像主题选择器, 允许限定符决定观察器将观察哪些事件。
使用@ qualifier定义的一个例子:
1
2
3
4
|
@Qualifier
@Target
({METHOD, FIELD, PARAMETER, TYPE})
@Retention
(RUNTIME)
public
@interface
Updated {}
|
另外,事件的创建和订阅是类型安全的.
一个观察者的处理方式是在方法中,加入一个参数注解@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) { ... }
|
Event producers的fire事件是使用参数化Event interface的实例.如下,通过@Inject注入该接口的一个实例.
1
|
@Inject
@Any
Event<Document> documentEvent;
|
而事件生产者通过调用fire()方法,并传递"事件对象"从而激活事件处理.
1
|
documentEvent.fire(document);
|
通过事件对象的参数值,容器调用所有观察者的方法,如果任何观察者方法抛出一个异常,容器会停止调用观察者方法,异常将会由fire()方法抛出。
Qualifiers 在事件中应用方式有两种:
@Inject @Updated Event<Document> documentUpdatedEvent;
1
|
public
void
afterDocumentUpdate(
@Observes
@Updated
Document document) { ... }
|
注解注入的缺点是,我们不能动态地指定限定符。
CDI也考虑到了这一点.
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()方法可以使用任意的注解组合在事件注入点和限定符实例上.
默认情况下,在当前上下文如果没有一个观察者的实例,容器将为事件实例化观察者.
但我们希望传递给观察者的实例是已经存在于上下文中的观察者.
指定一个有条件的观察者的方式是在@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!
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);
|
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并未包含在事件发生处.
事务处理的observers 在事务完成之前或之后的阶段才会收到事件通知.
例如,下面的观察方法需要在应用程序上下文中刷新一个查询的结果集,但是只有在 Category 更新成功才会执行:
1
|
public
void
refreshCategoryTree(
@Observes
(during = AFTER_SUCCESS) CategoryUpdateEvent event) { ... }
|
一共有五种transactional 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);
}
}
|
概述流程:
我在这里也是实际阐释一下.毕竟国内CDI方面的东西基本没有,也给学习CDI的朋友一个参考.
首先是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.
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的方法.就会去执行这个方法.\
这里就要继续写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