CDI(Weld)高级<4> Event(事件) (转)
目录[-]
以前发过一个粗略篇,已经删除.这次重新修订.
Cdi中的event事件,是整个CDI的精华所在之一.其有点类似设计模式中的观察者模式.但也有不同的地方.如下3点:
- 不仅是生产者(producers)从观察者(observers)解耦.观察者也从生产者解耦.
- 观察者可以指定“选择器”的组合来缩小的事件通知
- 观察者可以立即通知,或者可以指定交付的事件应该推迟到当前事务的结束。
即用一种维护生产者和观察者之间的分离代码的方式,来产生和订阅(即观察)在应用程序中发生的事件。使用 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 在事件中应用方式有两种:
- 在event的注入点(Injection point)通过注解
@Inject @Updated Event<Document> documentUpdatedEvent;
- 在观察者的处理方法中,通过事件对Qualifiers的选择.
1
public
void
afterDocumentUpdate(
@Observes
@Updated
Document document) { ... }
注解注入的缺点是,我们不能动态地指定限定符。
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:
- IN_PROGRESS --- observers被立即通知 (default)
- AFTER_SUCCESS --- 在事务成功完成后,observers会被通知.
- AFTER_FAILURE --- 在事务完成失败后,observers会被通知.
- AFTER_COMPLETION --- observers在交易完成后的阶段被调用
- 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
CDI(Weld)高级<4> Event(事件) (转)的更多相关文章
- [.NET] C# 知识回顾 - Event 事件
C# 知识回顾 - Event 事件 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6060297.html 序 昨天,通过<C# 知识回顾 - ...
- C# event 事件学习
C# event 事件学习 运行环境:Window7 64bit,.NetFramework4.61,C# 6.0: 编者:乌龙哈里 2017-02-26 章节: 简单事件编写 模拟 WPF 控件传递 ...
- Python 中Semaphore 信号量对象、Event事件、Condition
Semaphore 信号量对象 信号量是一个更高级的锁机制.信号量内部有一个计数器而不像锁对象内部有锁标识,而且只有当占用信号量的线程数超过信号量时线程才阻塞.这允许了多个线程可以同时访问相同的代码区 ...
- Event事件
妙味课堂-Event事件 1.焦点:当一个元素有焦点的时候,那么他就可以接受用户的输入(不是所有元素都能接受焦点) 给元素设置焦点的方式: 1.点击 2.tab 3.js 2.(例子:输入框提示文字) ...
- JS学习笔记9之event事件及其他事件
-->鼠标事件-->event事件对象-->默认事件-->键盘事件(keyCode)-->拖拽效果 一.鼠标事件 onclick ---------------鼠标点击事 ...
- JS(event事件)
常用的event事件: 属性 此事件发生在何时... onabort 图像的加载被中断. onblur 元素失去焦点. onchange 域的内容被改变. onclick 当用户点击某个对象时调用的事 ...
- event事件学习小节
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Javascript 事件对象(二)event事件
Event事件: <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" ...
- js event 事件兼容浏览器 ie不需要 event参数 firefox 需要
js event 事件兼容浏览器 ie不需要 event参数 firefox 需要 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 ...
随机推荐
- HDU 1717 小数化分数2(最大公约数)
小数化分数2 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Subm ...
- mysql5.7.12/13在安装新实例时报错:InnoDB: auto-extending data file ./ibdata1 is of a different size 640 pages (rounded down to MB) than specified in the .cnf file: initial 768 pages, max 0 (relevant if non-zero
.bin/mysqld --initialize-insecure --basedir=xxx --datadir=xxx 然后 .bin/mysqld_safe --defaults-file=xx ...
- app crawler1
app crawler简介 执行 java -jar appcrawler-2.1.3.jar 查看相关参数 -a, --app Android或者iOS的文件地址, 可以是网络地址, 赋值给appi ...
- hihocoder-1080题解
一.题目链接 http://hihocoder.com/problemset/problem/1080 二.题意 一维区间,需要做区间增加和区间置值,以及对整个区间求和. 三.思路 显然线段树是个利器 ...
- fdisk用法(转载)
Linux下的fdisk功能是极其强大的,用它可以划分出最复杂的分区,下面简要介绍一下它的用法: 对于IDE硬盘,每块盘有一个设备名:对应于主板的四个IDE接口,设备名依次为:/dev/hda,/de ...
- Android开发——JVM、Dalvik以及ART的区别【转帖】
转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52354964 0. 前言 Dalvik是Google公司自己设计用于Android平 ...
- Vim插件之ale,LeaderF,completor.vim(win10)配置
内容包含 vim-plug,异步插件管理,总之就是下起来快. ale,异步语法检查 LeaderF,快速查找文件 completor.vim vim8的快速补全 markdown预览 common s ...
- 用dwz时, 由于粗心产生的一些问题(记录方便自己查阅)
在打开"添加" 或 "修改" , 用dialog弹出时 , 点击提交的时候, dialog 不能关闭, 也不能刷新 解决办法: 注意form标签, onsubm ...
- NotePad++替换行前、行后空格,替换空行
用 Notepad++ 打开,把每一个将要放在表中单元格的内容放一行(注: ^ 代表行首 $ 代表行尾) 去除行尾空格和空白行:按CTRL+H 选择正则表达式– 查找目标:\s+$ 替换为空 去除行首 ...
- lda spark 代码官方文档
http://spark.apache.org/docs/1.6.1/mllib-clustering.html#latent-dirichlet-allocation-lda http://spar ...