你有一件事情,做这件事情的过程包含了许多职责单一的子过程。这样的情况及其常见。当这些子过程有如下特点时,我们应该考虑设计一种合适的框架,让框架来完成一些业务无关的事情,从而使得各个子过程的开发可以专注于自己的业务。

  • 这些子过程有一定的执行次序;
  • 这些子过程之间需要较灵活的跳转;
  • 这些子过程也许需要围绕同一个上下文做操作;

此时可以考虑使用事件驱动的方式来组织这些子过程,此时这些子过程可以被称之为事件处理器(或监听器),而将事件处理器组织起来的管理者,叫做事件中心。最显而易见的实现方式,是观察者模式,或者监听者模式。作为一个例子,考虑一个消息转发系统,它从上游接收消息,然后转发给正确的下游用户。整个过程可以拆分为消息解析、消息存储、消息发送等步骤。

事件Event

首先定义事件Event。事件将作为一个基本元素,在处理器和事件中心之间建立其连线。这里为了能够统一处理异常。以及针对异常打出日志,除了业务相关的事件,还增加了异常事件和日志事件。当然相应的也应该新增与之对应的事件处理器。

  1. package me.test.eventcenter;
  2.  
  3. /**
  4. * Created by chng on 2015/12/18.
  5. */
  6. public class EventName {
  7.  
  8. private final String name;
  9. public EventName(String name) {
  10. this.name = name;
  11. }
  12.  
  13. public static EventName msg_received = new EventName("msg_received");
  14. public static EventName msg_resolved = new EventName("msg_resolved");
  15. public static EventName msg_stored = new EventName("msg_stored");
  16. public static EventName msg_pushed = new EventName("msg_pushed");
  17. public static EventName exception_occured = new EventName("exception_occured");
  18. public static EventName end_and_log = new EventName("end_and_log");
  19.  
  20. public String getName() {
  21. return name;
  22. }
  23. }

事件处理器 EventHandler

随后,定义一个简单的事件处理器的抽象类,其中包含一个单例的事件中心,每个处理器通过持有这个事件中心来执行注册自己(即订阅一个事件)和呼起下一个事件的操作。

  1. package me.test.eventcenter.handler;
  2.  
  3. import me.test.eventcenter.EventCenter;
  4. import org.springframework.beans.factory.InitializingBean;
  5.  
  6. import javax.annotation.Resource;
  7.  
  8. /**
  9. * Created by chng on 2015/12/18.
  10. */
  11. public abstract class EventHandler implements InitializingBean {
  12. @Resource
  13. EventCenter eventCenter;
  14.  
  15. public abstract void handle(Object ... param);
  16. }

事件中心 EventCenter

有了事件和事件处理器,接下来定义一个事件中心,将二者粘起来。

  1. package me.test.eventcenter;
  2.  
  3. import com.google.common.collect.Lists;
  4. import com.google.common.collect.Maps;
  5. import me.test.eventcenter.handler.EventHandler;
  6. import org.springframework.stereotype.Component;
  7. import org.springframework.util.CollectionUtils;
  8.  
  9. import java.util.List;
  10. import java.util.Map;
  11.  
  12. /**
  13. * Created by chng on 2015/12/18.
  14. */
  15. @Component
  16. public class EventCenter {
  17.  
  18. private Map<EventName, List<EventHandler>> regTable = Maps.newHashMap();
  19.  
  20. /**
  21. * 向事件中心广播一个时间,驱使事件中心执行该事件的处理器
  22. * @param eventName
  23. * @param param
  24. */
  25. public void fire(EventName eventName, Object ... param) {
  26. System.out.println(eventName.getName());
  27. List<EventHandler> handlerList = regTable.get(eventName);
  28. if(CollectionUtils.isEmpty(handlerList)) {
  29. // log
  30. return;
  31. }
  32. for(EventHandler handler: handlerList) {
  33. try {
  34. handler.handle(param);
  35. } catch (Exception e) {
  36. fire(EventName.exception_occured, e);
  37. }
  38. }
  39. }
  40.  
  41. /**
  42. * 将自己注册为事件中心的某个事件的处理器
  43. * @param eventName
  44. * @param handler
  45. */
  46. public void register(EventName eventName, EventHandler handler) {
  47.  
  48. List<EventHandler> handlerList = regTable.get(eventName);
  49. if(null == handlerList) {
  50. handlerList = Lists.newLinkedList();
  51. }
  52.  
  53. handlerList.add(handler);
  54. regTable.put(eventName, handlerList);
  55. }
  56. }

在事件中心中,事件和处理器之间的关系表示为一个HashMap,每个事件可以被多个处理器监听,而一个处理器只能监听一个事件(这样的关系并非是固定的,也可在运行时动态地改变)。当呼起一个事件时,事件中心找到该事件的监听者,逐个调用他们的处理方法。将各子模块的执行集中在这里管理,还有两个额外的好处:

1 如果发生异常,则呼起异常处理器。这样,一旦业务模块发生了不得不终止整个过程的时候,不需要自己写try/catch子句,而只需要将异常往上抛,直到抛给框架层,由它来做这些统一的事情。然而这并不意味着各业务模块彻彻底底地摆脱了难看的try/catch/finally,运行时发生的异常被catch后,并非都可以直接END,何去何从仍然视情况而定,直接将异常吞掉也未尝不可能。

2 打日志的活儿交给EventCenter就好了,没人比它更清楚当前执行到了哪一步。而各子模块里面,可以省去许多散布在各处的日志语句。对于散弹式日志的问题,解决方法不止一种,AOP也是个不错的选择。

测试

为了让整个过程跑起来,我们只需要发起一个初始的事件,将所有的事件处理器都依次驱动起来:

  1. /**
  2. * Created by OurEDA on 2015/12/18.
  3. */
  4. public class TestEventCenter extends BaseTest {
  5.  
  6. @Resource
  7. EventCenter eventCenter;
  8.  
  9. @Test
  10. public void test() {
  11. RawMessage rawMessage = new RawMessage("NotifyType: amq");
  12. rawMessage.setType(RawMessage.MessageType.amq);
  13. eventCenter.fire(EventName.msg_received, notify);
  14. }
  15. }

以测试通过为目标,我们开始定义一系列的EventHandler,并将这些Handler注册到合适的事件上。例如一个消息解析的Handler,对msg_receive事件感兴趣,解析完成后将发起msg_store事件,那么:

  1. package me.test.eventcenter.handler;
  2.  
  3. import me.test.eventcenter.*;
  4. import me.test.messagedo.Message;
  5. import me.test.messagedo.RawMessage;
  6. import me.test.resolvers.MsgResolverList;
  7. import org.springframework.beans.factory.InitializingBean;
  8. import org.springframework.stereotype.Component;
  9.  
  10. import javax.annotation.Resource;
  11.  
  12. /**
  13. * Created by chng on 2015/12/18.
  14. */
  15. @Component
  16. public class MsgResolveHandler extends EventHandler implements InitializingBean {
  17.  
  18. @Resource
  19. private MsgResolverList resolverList;
  20.  
  21. @Override
  22. public void handle(Object... param) {
  23.  
  24. /**
  25. * Resolver
  26. */
  27. RawMessage rm = (RawMessage) param[0];
  28. Message message = resolverList.resolve(rm);
  29. eventCenter.fire(EventName.msg_resolved, message);
  30. }
  31.  
  32. public void afterPropertiesSet() throws Exception {
  33. eventCenter.register(EventName.msg_received, this);
  34. }
  35. }

可以看到,对象在初始阶段把自己(this)注册到了事件中心里。handler方法则只关心如何解析消息,不需要关系别的事情。针对不同类型的消息,解析器可以写成Map的形式,一种类型对应一个解析器;如果消息的分类比较复杂,还可以写成职责链的形式当然这都无关紧要,我们需要知道的是,这个模块只解析消息,与其他子模块之间是完全解耦的。

例如,一种可能的解析器组合体是这样的:

  1. MsgResolver.java (interface)
  1. package me.test.resolvers;
  2.  
  3. import me.test.messagedo.Message;
  4. import me.test.messagedo.RawMessage;
  5.  
  6. /**
  7. * Created by OurEDA on 2015/12/18.
  8. */
  9. public interface MsgResolver {
  10.  
  11. public boolean canResolve(RawMessage rm);
  12.  
  13. public Message resolve(RawMessage rm);
  14.  
  15. }
  1. MsgResolverList.java
  1. package me.test.resolvers;
  2.  
  3. import me.test.messagedo.Message;
  4. import me.test.messagedo.RawMessage;
  5. import org.springframework.stereotype.Component;
  6.  
  7. import java.util.List;
  8.  
  9. /**
  10. * Created by chng on 2015/12/18.
  11. */
  12. @Component
  13. public class MsgResolverList implements MsgResolver{
  14.  
  15. //职责链
  16. private List<MsgResolver> resolvers;
  17. public List<MsgResolver> getResolvers() {
  18. return resolvers;
  19. }
  20. public void setResolvers(List<MsgResolver> resolvers) {
  21. this.resolvers = resolvers;
  22. }
  23.  
  24. public boolean canResolve(RawMessage rawMessage) {
  25. return true;
  26. }
  27.  
  28. public Message resolve(RawMessage rawMessage) {
  29. for(MsgResolver resolver: resolvers) {
  30. if(resolver.canResolve(rawMessage)) {
  31. System.out.println("NotifyType: "+rawMessage.type);
  32. return resolver.resolve(rawMessage);
  33. }
  34. }
  35. return null;
  36. }
  37. }

不必额外打日志,用例的输出是这样的:

哪一步出了问题,出了什么问题,通通一目了然。

其他:

1 上下文 Context

各个处理器都围绕一个上下文做处理,此例为了体现通用性,上下文直接用Object表示。在实际的场景下,则需要一个统一的结构体。不同的Handler将对该统一上下文的不同内容感兴趣。

2 线程封闭 ThreadLocal

当有多个线程都在事件中心中进行周转时,还需要考虑线程安全问题,保证线程的调度不会对事件处理器的呼起次序造成干扰。因此整个事件中心和上下文,都需要做隔离。

3 反思

上面这种写法有两个明确的缺点:事件的注册操作写死在每个处理器的初始化代码中,一来缺乏灵活性,二来对于各Handler是如何组织起来的,没有一个统一而清晰的bigmap。

【Java】事件驱动模型和观察者模式的更多相关文章

  1. (六)观察者模式详解(包含观察者模式JDK的漏洞以及事件驱动模型)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 本章我们讨论一个除前面的单例 ...

  2. 设计模式之 观察者模式详解(包含观察者模式JDK的漏洞以及事件驱动模型)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 本章我们讨论一个除前面的单例 ...

  3. [案例一] Spring中的事件驱动模型(机制)

    事件驱动模型是观察者模式的另一种形态,观察者相当于监听器,被观察者相当于事件源 事件源产生事件,监听器监听事件 以用户注册时候,要发送邮件和发送短信举例说明 定义一个事件 /** * spring会自 ...

  4. 带你自定义实现Spring事件驱动模型

    Spring 事件驱动模型概念 Spring 事件驱动模型就是观察者模式很经典的一个应用,我们可以通过Spring 事件驱动模型来完成代码的解耦. 三角色 Spring 事件驱动模型或者说观察者模式需 ...

  5. 一个I/O线程可以并发处理N个客户端连接和读写操作 I/O复用模型 基于Buf操作NIO可以读取任意位置的数据 Channel中读取数据到Buffer中或将数据 Buffer 中写入到 Channel 事件驱动消息通知观察者模式

    Tomcat那些事儿 https://mp.weixin.qq.com/s?__biz=MzI3MTEwODc5Ng==&mid=2650860016&idx=2&sn=549 ...

  6. 事件驱动模型实例详解(Java篇)

    或许每个软件从业者都有从学习控制台应用程序到学习可视化编程的转变过程,控制台应用程序的优点在于可以方便的练习某个语言的语法和开发习惯(如.net和java),而可视化编程的学习又可以非常方便开发出各类 ...

  7. Java学习疑惑(8)----可视化编程, 对Java中事件驱动模型的理解

    我们编写程序就是为了方便用户使用, 我觉得UI设计的核心就是简洁, 操作过于繁琐的程序让很大一部分用户敬而远之. 即使功能强大, 但是人们更愿意使用易于操作的软件. 近年流行起来的操作手势和逐渐趋于成 ...

  8. spring事件驱动模型--观察者模式在spring中的应用

    spring中的事件驱动模型也叫作发布订阅模式,是观察者模式的一个典型的应用,关于观察者模式在之前的博文中总结过,http://www.cnblogs.com/fingerboy/p/5468994. ...

  9. 事件驱动模型的简单Java实现

    事件驱动模型的原理不再赘述,Swing是不错的实现.别人也有不错的博文来说明原理. 本文的目的是提供一种简单的,可供参考的简短代码,用来帮助理解该模型. Project Navigator Event ...

随机推荐

  1. 【BZOJ1299】巧克力棒(Nim游戏,SG函数)

    题意:TBL和X用巧克力棒玩游戏.每次一人可以从盒子里取出若干条巧克力棒,或是将一根取出的巧克力棒吃掉正整数长度. TBL先手两人轮流,无法操作的人输. 他们以最佳策略一共进行了10轮(每次一盒).你 ...

  2. http://www.codeproject.com/KB/validation/MultipleDataAnnotations.aspx

    原文发布时间为:2011-08-12 -- 来源于本人的百度文章 [由搬家工具导入] http://www.codeproject.com/KB/validation/MultipleDataAnno ...

  3. 移动GIS技术在城市信息采集中的应用

    1 引言 随着移动平板电脑和手机(以下简称移动终端)在软硬件上的更新换代,和3G.4G通讯网络的升级,传统测绘和和数据服务方式正在发生巨大变化.以城市中的外业踏勘和信息采集为例,移动终端正成为主要的外 ...

  4. python-rtslib 模块

    Python library for configuring the Linux kernel-based multiprotocol SCSI target (LIO) A Python objec ...

  5. poj 2299(离散化+树状数组)

    Ultra-QuickSort Time Limit: 7000MS   Memory Limit: 65536K Total Submissions: 53777   Accepted: 19766 ...

  6. 2014年国内最热门的.NET开源平台

    http://developer.51cto.com/art/201501/464292.htm

  7. android studio 按钮运行按钮后,不弹出选择运行模拟器的对话框

    这个问题实际上很简单,奈何碰到的时候做了很多无用功.clean,rebulid... 特此记录,方便后来人. 解决步骤: 1.关闭AndroidStudio,并重启. 2. 把截图中的地方的勾去掉.检 ...

  8. Codeforces 583 DIV2 Asphalting Roads 模拟

    原题链接:http://codeforces.com/problemset/problem/583/A 题意: 很迷很迷,表示没看懂..但是你看样例就秒懂了 题解: 照着样例模拟就好 代码: #inc ...

  9. 《Microsoft SQL Server 2008 Internals》读书笔记--目录索引

    http://blog.csdn.net/downmoon/article/details/5256548 https://sqlserverinternals.com/companion/

  10. Jenkins introduction

    http://birdinroom.blog.51cto.com/7740375/1342897 https://www.ibm.com/developerworks/cn/java/j-lo-jen ...