• 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

  • 相关阅读:
    Vue项目中跨域问题解决
    子网掩码
    C++的const类成员函数
    在python3中使用urllib.request编写简单的网络爬虫
    Linux 重定向输出到多个文件中
    背包问题
    hdu-1272 小希的迷宫
    SQLAlchemy 几种查询方式总结
    pycharm快捷键、常用设置、配置管理
    python3判断字典、列表、元组为空以及字典是否存在某个key的方法
  • 原文地址:https://www.cnblogs.com/juepei/p/3854796.html
Copyright © 2020-2023  润新知