在项目开发过程中,往往有些功能表面看起来简单,但实际开发的结果非常复杂,仔细分析下原因发现很多都是因为附加了许多的额外功能。

真的简单吗?

比如我们对一个电商平台的商品数据做修改的功能来讲,其实非常简单,无非就是运营人员在管理平台中对商品进行修改数据,然后点击提交,核心功能的确很简单,但可能有人会要求对商品的修改都需要增加操作日志,还有人提出需要在商品数据修改后自动去更新检索系统中的数据,有人提要在商品数据修改后需要经过审核人的审核才能生效,还有人提需要给运营人员发邮件通知等等,如此一来这个商品修改的功能就不是那么简单了,它除了完成自己的使命,还需要去调用其它的服务来完成,活动类似如下:

横向的主流程,上下四个小方框是附加功能,复杂的原因包含如下两点:

  • 附加功能导致功能开点变多,工作量加大
  • 附加功能导致程序逻辑复杂,在程序中需要去访问其它的服务,还需要考虑数据完整性,性能,服务依赖等各种问题。

刚开始我们在项目中开发时,使用的就是最简单的强耦合去直接调用服务,比如在调用完数据保存的方法后,去调用logService的log方法记录日志,调用esService的方法去更新检索系统,调用mailService的方法去发邮件,这样会导致我们的productService强耦合这些与商品保存逻辑没有直接关联的服务,这样看起来商品保存的功能变得不那么单纯,也就是我们文前提到的复杂了,会出现类似代码:

  1. @Autowired
  2. private SearchService searchService;
  3. @Autowired
  4. private MailService mailService;
  5. @Autowired
  6. private ProductLogService productLogService;
  7.  
  8. //如下下保存商品的代码片段
  9. itemDao.save(product);
  10. searchService.update(product.getId());
  11. mailService.post(product.getId());
  12. logService.log(product.getId());

问题如下:

  • 对无业务直接关联的服务强耦合
  • 可能存在性能问题,比如你需要关心邮件发送是否会影响主流程
  • 代码可读性变差,过多的逻辑容易导致分不清主体核心功能

如果解决呢?有一个设计模式可以解决,那就是观察者模式,之前学习.net时专门写过一篇(老生常谈:观察者模式),里面提到有传统的实现方式以及事件机制,事件机制的实现比较简单一些,这里我们在解决这个问题时引用了guava组件中提供的eventbus,它与之前那篇观察者模式的实现很相似,看下面这张图,功能之间没有错综复杂的依赖。

注:guava的eventbus是个进程内级别的,无法跨进程,后面我抽时间再整理下基于消息队列的分布式事件总结。

这里我并不介绍如何使用EventBus,而是重点来说明我们项目中对它的应用,哪些是做的不好的地方。

第一:要有观察者,这里我们根据不同的业务封装不同的观察者,下面是更新检索系统数据的类

  1. @Service
  2. public class SearchEventListener {
  3. @Autowired()
  4. ProductUpdateMgr productSearchUpdateMgr;
  5. private final static Logger logger = LoggerFactory.getLogger(SearchEventListener.class);
  6. @Subscribe
  7. public void listen(String itemLegacyId) {
  8. try
  9. {
  10. productSearchUpdateMgr.markProductDirty(itemLegacyId);
  11.  
  12. }
  13. catch(Exception ex)
  14. {
  15. logger.error("更新检索异常:" + ex.getMessage() + ex.getStackTrace());
  16. }
  17. }
  18. }

第二:需要有将观察者注册到eventbus中去,我们专门写了一个类来做这件事情,完成两件事情:

  • 注册观察者到eventbus中
  • 进一步包装post方法以便调用者以服务形式调用,让productService依赖eventbus而不是依赖实际的检索服务,邮件服务等
  1. @Service
  2. public class EventListenerManager {
  3. EventBus mmsProductEventBus;
  4. EventBus mmsItemEventBus;
  5. EventBus mmsSearchEventBus;
  6. EventBus mmsRebuildAllSearchEventBus;
  7. EventBus mmsCommonLogEventBus;
  8. @Autowired
  9. ItemEventListener itemListener;
  10. @Autowired
  11. ProductEventListener productListener;
  12. @Autowired
  13. SearchEventListener searchListener;
  14. @Autowired
  15. RebuildAllSearchEventListener rebuildAllSearchListener;
  16. @Autowired
  17. MmsCommonLogEventListener commonLogListener;
  18.  
  19. @PostConstruct
  20. private void init() {
  21. mmsProductEventBus = new EventBus();
  22. mmsItemEventBus = new EventBus();
  23. mmsSearchEventBus = new EventBus();
  24. mmsRebuildAllSearchEventBus=new EventBus();
  25. mmsCommonLogEventBus=new EventBus();
  26. mmsItemEventBus.register(itemListener);
  27. mmsProductEventBus.register(productListener);
  28. mmsSearchEventBus.register(searchListener);
  29. mmsRebuildAllSearchEventBus.register(rebuildAllSearchListener);
  30. mmsCommonLogEventBus.register(commonLogListener);
  31. }
  32.  
  33. public void notifyItemLog(Long itemId) {
  34. mmsItemEventBus.post(itemId);
  35. }
  36.  
  37. public void notifyProductLog(Long productId) {
  38. mmsProductEventBus.post(productId);
  39. }
  40.  
  41. public void notifyUpdateSearch(String itemLegacyId) {
  42. mmsSearchEventBus.post(itemLegacyId);
  43. }
  44. public void notifyRebuildAllSearch() {
  45. mmsRebuildAllSearchEventBus.post("");
  46. }
  47. public void notifyAddCommonLog(MmsCommonLogModel log) {
  48. mmsCommonLogEventBus.post(log);
  49. }
  50. }

上面的实现以及我们在刚学习使用guava eventbugs时遇到哪些问题呢?
  问题一:为什么上面的代码有这么多的eventbus而不是一个呢?注意下enventbus的post方法,我们再看下它的源码:它是根据参数的类型来找观察者注册的方法的,而我们写的观察者类中的方法中的参数都是一些primitive类型的,总共有10个左右方法,要想根据参数类型来正确的在一个eventbus中识别调用哪个方法,是比较困难的。

  1. public void post(Object event) {
  2. Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());
  3.  
  4. boolean dispatched = false;
  5. for (Class<?> eventType : dispatchTypes) {
  6. subscribersByTypeLock.readLock().lock();
  7. try {
  8. Set<EventSubscriber> wrappers = subscribersByType.get(eventType);
  9.  
  10. if (!wrappers.isEmpty()) {
  11. dispatched = true;
  12. for (EventSubscriber wrapper : wrappers) {
  13. enqueueEvent(event, wrapper);
  14. }
  15. }
  16. } finally {
  17. subscribersByTypeLock.readLock().unlock();
  18. }
  19. }
  20.  
  21. if (!dispatched && !(event instanceof DeadEvent)) {
  22. post(new DeadEvent(this, event));
  23. }
  24.  
  25. dispatchQueuedEvents();
  26. }

如何解决?可以针对每个方法的参数封装一个类,比如更新检索的方法参数叫SearchChangeEvent,发送审核邮件的参数叫ApprovalChangeEvent等等,这样我们就可以将所有的观察者注册到一个eventbus中,调用post方法时就不会出现问题了,最后简化后的结果如下:

  1. @Autowired
  2. EventListenerManager eventManager;
  3.  
  4. //如下下保存商品的代码片段
  5. itemDao.save(product);
  6. eventManager.post(new SearchChangeEvent(product.getId));
  7. eventManager.post(new MailChangeEvent(product.getId));
  8. eventManager.post(new LogChangeEvent(product.getId));

问题二:性能问题,之前有提到过附加的功能会导致原本单纯的事物不单纯,比如调用某些服务时可能影响整体性能,当时我们想当然的认为使用了enventbus本身就是异步的,其实如果用eventbus它是同步的,要想使用异步需要使用这个类来完成AsyncEventBus。

问题三:强耦合问题,由于有了EventListenerManager,我们在具体业务中就不需要依赖不直接相关的服务了,只需要依赖EventListenerManager这个看起来与任务业务都无关的管理类就可以了。


  通过实际项目中对eventbus的应用来分析它能解决的问题以及当初应用有待提高的地方。很显示eventbus应用得当可以简化程序复杂性,提高代码可读性,降低开发维护成本。

项目中应用eventbus解决的问题的更多相关文章

  1. loadrunner解决在项目中的难点解决

    代码如下: vuser_init() { lr_save_string("11041331\",\"11041372\",\"11041373\&qu ...

  2. .net 项目中cookie丢失解决办法

    创建cookie的时候 HttpCookie PdaCookie = new HttpCookie("Pda");PdaCookie ["PdaId"] = 1 ...

  3. 针对MSHFlexGrid的一系列通用方法-项目中实践代码分享

    1.给MSHFlexGrid填充数据通用方法 '自定义报表填充程序 fgrid Public Function ShowformfData(Resultset As ADODB.Recordset, ...

  4. eclipse导入web项目之后项目中出现小红叉解决办法

    项目中有小红叉我遇到的最常见的情况: 1.项目代码本身有问题.(这个就不说了,解决错误就OK) 2.项目中的jar包丢失.(有时候eclipse打开时会出现jar包丢失的情况,关闭eclipse重新打 ...

  5. 解决项目中.a文件的冲突

    .a文件是静态文件,有多个.o文件组合而成的,在ios项目开发中,当引用第三方库的时候,时不时的会碰到诸如库冲突.库包含了某些禁用的API等问题,而这些库往往都被打包成了静态库文件(即 .a文件)来使 ...

  6. 转 mvc项目中,解决引用jquery文件后智能提示失效的办法

    mvc项目中,解决用Url.Content方法引用jquery文件后智能提示失效的办法   这个标题不知道要怎么写才好, 但是希望文章的内容对大家有帮助. 场景如下: 我们在用开发开发程序的时候,经常 ...

  7. eclipse项目中关于导入的项目里提示HttpServletRequest 不能引用的解决办法

    eclipse项目中关于导入的项目里提示HttpServletRequest 不能引用的解决办法 当使用eclipse导入外部的web工程时,有时会提示HttpServletRequest, Serv ...

  8. 在maven项目中解决第三方jar包依赖的问题

    在maven项目中,对于那些在maven仓库中不存在的第三方jar,依赖解决通常有如下解决方法: 方法1:直接将jar包拷贝到项目指定目录下,然后在pom文件中指定依赖类型为system,如: < ...

  9. Npoi Web 项目中(XSSFWorkbook) 导出出现无法访问已关闭的流的解决方法

    原本在CS项目中用的好好的在BS项目中既然提示我导出出现无法访问已关闭的流的解决方法 比较郁闷经过研究 终于解决了先将方法发出来 让遇到此问题的筒子们以作参考 //新建类 重写Npoi流方法 publ ...

随机推荐

  1. 国内常用的三种框架:ionic/mui/framework7对比

    国内常用的三种框架:ionic/mui/framework7对比 原文连接:http://zhihu.com/question/19558750/answer/91179040

  2. 快速入门系列--MVC--04模型

    model元数据 闲来继续学习蒋金楠大师的ASP.NET MVC框架揭秘一书,当前主要阅读的内容是Model元数据的解析,即使是阅读完的现在,仍然有不少细节不是特别明白.好在这部分内容主要是关于Raz ...

  3. js中如果省略分号那么它是如何运行的

    在javascript工作中,我们几乎不会去省略分号:为了不必要的麻烦以及代码的规范,那么如果我们省略:会发生呢?预知详情请听下回分解. 看代码! 片段一: 1 var a 2 = 3 8 4 con ...

  4. GDB 和 windbg 命令对照(转载)

    From:http://blog.csdn.net/joeleechj/article/details/10020501 命令                                      ...

  5. [logstash-input-log4j]插件使用详解

    Log4j插件可以通过log4j.jar获取Java日志,搭配Log4j的SocketAppender和SocketHubAppender使用,常用于简单的集群日志汇总. 最小化的配置 input { ...

  6. SQL Server安全(1/11):SQL Server安全概述

    在保密你的服务器和数据,防备当前复杂的攻击,SQL Server有你需要的一切.但在你能有效使用这些安全功能前,你需要理解你面对的威胁和一些基本的安全概念.这篇文章提供了基础,因此你可以对SQL Se ...

  7. Asp.net 高性能 Sqlite ORM 框架之 sqliteSugar

    一.介简 easyliter框架的升级版本,并且正式命名为SqliteSugar框架,另外Sugar系列还有 MySql和MsSql版本,Oracle版本待开发中(因为客户端太大一直在忧郁当中) 用S ...

  8. Spark API 之 combineByKey(一)

    1       前言 combineByKey是使用Spark无法避免的一个方法,总会在有意或无意,直接或间接的调用到它.从它的字面上就可以知道,它有聚合的作用,对于这点不想做过多的解释,原因很简单, ...

  9. ES6笔记(7)-- Promise异步编程

    系列文章 -- ES6笔记系列 很久很久以前,在做Node.js聊天室,使用MongoDB数据服务的时候就遇到了多重回调嵌套导致代码混乱的问题. JS异步编程有利有弊,Promise的出现,改善了这一 ...

  10. html的块级、内联、内联块级元素基础

    概念 块级:block 内联:inline 内联块级:inline-block 在html元素中,元素会有display属性 display属性默认值是block,那么该元素是块级元素. displa ...