
6.3 注册事件监听器
下面详细分析全局事件监听器和具体类型的事件监听器是如何注册使用的。事件监听器从应用范围上可以划分为两种:一种是全局事件监听器,负责监听所有的事件;另一种是具体类型的事件监听器,只负责部分事件的监听,例如开发人员可以将所有流程引擎支持的事件按照业务使用场景进行划分,可以定义一个类只负责监听所有与任务有关的事件,也可以定义一个类只负责变量事件的监听等,引擎设计具体类型的事件监听器的主要意图是方便开发人员细化、归类事件,从而使事件监听器的职责更加清晰和单一,代码也更加容易维护。
上面提到事件监听器的注册和移除工作最终是通过ActivitiEventDispatcherImpl类中持有的ActivitiEventSupport实例对象来完成,下面详细分析ActivitiEventSupport类是如何进行事件监听器的注册工作,整个处理流程如代码清单6-2所示。
代码清单6-2 ActivitiEventSupport.java

(1)第1行定义的addEventListener方法用于注册全局事件监听器。
addEventListener方法主要用于注册全局事件监听器,该方法的处理逻辑非常简单,首先第2行对listenerToAdd参数进行非空校验,如果该参数不为空,则第3行判断eventListeners集合中是否已经存在了listenerToAdd,如果集合中不存在,则第4行将其添加到集合中,该操作主要是为了避免重复注册事件监听器。
(2)第7行定义的addEventListener方法用于注册具体类型的事件监听器。
首先第8行对listenerToAdd参数进行非空校验,然后第9行判断type参数是否为空,如果type参数为空则第10行直接将当前的事件监听器作为全局事件监听器进行处理,并通过第1行定义的方法将其注册到eventListeners集合中;如果type参数不为空,则第12~13行循环遍历type数组,并调用第17行定义的addTypedEventListener方法注册具体类型的事件监听器,该方法的处理逻辑如下。
• 第18行根据type参数从typedListeners集合中获取该事件下所有的事件监听器集合listeners。
• 第19行如果listeners集合为空,则首先初始化该集合并将其添加到typedListeners集合中。
• 第23行如果listeners集合中不存在listener元素,则将其添加到listeners集合中。
因为向eventListeners集合中添加元素时,可能存在多个线程同时对集合内容进行操作,所以需要考虑集合数据同步问题,因此上面三个方法中的方法签名均使用了synchronized关键字进行修饰。
ActivitiEventSupport类中的eventListeners集合为List数据结构,初始化该集合时使用了Java中的CopyOnWriteArrayList类,该集合的初始化过程可以参考ActivitiEventSupport类的构造方法。这里需要对CopyOnWriteArrayList类详细说明:CopyOnWriteArrayList类非常重要,可以使用在高并发场景中,基本思路是这样的,例如存在一个集合,多个线程都要对集合内容进行操作,当任意一个线程想要改变集合内容时,首先需要把集合中的内容进行一次全复制并形成一个新的容器,这样任何一个线程想要对原容器中的内容进行修改操作时,只会修改新生成容器中的内容,修改完毕之后将原容器的指针指向新容器,从而达到修改集合内容的目的,这是一种典型的懒惰延时策略,这样做的好处就是每次修改容器内容时操作的永远是新容器,从而可以对CopyOnWriteArrayList进行并发读而不需要加同步锁,由于当前容器集合永远不会添加新元素,读写操作永远在不同的容器中进行,是典型的读写分离思想。
CopyOnWriteArrayList类的缺点:在写操作过程中内存会同时存在两个容器,这样程序就会占用大量的内存,并且写操作过程客户端可能需要从原容器中读取最新的数据,而原容器的指针在指向新容器之前程序读取的永远是原容器的值,这样可能会造成数据短时间内不一致,如果开发人员期望写数据立刻就能被读取到,必须要求保证数据的实时性、一致性,那么很显然使用CopyOnWriteArrayList容器不是一种明智的选择。
注意
Java中的Map为引用类型,如果获取到某个Map的引用,则程序操作引用对象的时,本质上就是对Map进行操作。