你的位置:首页 > Java教程

[Java教程]项目中应用eventbus解决的问题


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

  真的简单吗?

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

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

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

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

@Autowiredprivate SearchService searchService;@Autowiredprivate MailService mailService;@Autowiredprivate ProductLogService productLogService;//如下下保存商品的代码片段itemDao.save(product);searchService.update(product.getId());mailService.post(product.getId());logService.log(product.getId());

  问题如下:

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

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

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

 

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

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

@Service public class SearchEventListener {  @Autowired()  ProductUpdateMgr productSearchUpdateMgr;  private final static Logger logger = LoggerFactory.getLogger(SearchEventListener.class);  @Subscribe  public void listen(String itemLegacyId) {    try    {      productSearchUpdateMgr.markProductDirty(itemLegacyId);    }    catch(Exception ex)    {      logger.error("更新检索异常:" + ex.getMessage() + ex.getStackTrace());    }  }  }


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

  • 注册观察者到eventbus中
  • 进一步包装post方法以便调用者以服务形式调用,让productService依赖eventbus而不是依赖实际的检索服务,邮件服务等
@Servicepublic class EventListenerManager {  EventBus mmsProductEventBus;  EventBus mmsItemEventBus;  EventBus mmsSearchEventBus;  EventBus mmsRebuildAllSearchEventBus;  EventBus mmsCommonLogEventBus;  @Autowired  ItemEventListener itemListener;  @Autowired  ProductEventListener productListener;  @Autowired  SearchEventListener searchListener;  @Autowired  RebuildAllSearchEventListener rebuildAllSearchListener;  @Autowired  MmsCommonLogEventListener commonLogListener;  @PostConstruct  private void init() {    mmsProductEventBus = new EventBus();    mmsItemEventBus = new EventBus();    mmsSearchEventBus = new EventBus();    mmsRebuildAllSearchEventBus=new EventBus();    mmsCommonLogEventBus=new EventBus();    mmsItemEventBus.register(itemListener);    mmsProductEventBus.register(productListener);    mmsSearchEventBus.register(searchListener);    mmsRebuildAllSearchEventBus.register(rebuildAllSearchListener);    mmsCommonLogEventBus.register(commonLogListener);  }  public void notifyItemLog(Long itemId) {    mmsItemEventBus.post(itemId);  }  public void notifyProductLog(Long productId) {    mmsProductEventBus.post(productId);  }  public void notifyUpdateSearch(String itemLegacyId) {    mmsSearchEventBus.post(itemLegacyId);  }  public void notifyRebuildAllSearch() {    mmsRebuildAllSearchEventBus.post("");  }  public void notifyAddCommonLog(MmsCommonLogModel log) {    mmsCommonLogEventBus.post(log);  }} 

 

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

public void post(Object event) {  Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());  boolean dispatched = false;  for (Class<?> eventType : dispatchTypes) {   subscribersByTypeLock.readLock().lock();   try {    Set<EventSubscriber> wrappers = subscribersByType.get(eventType);    if (!wrappers.isEmpty()) {     dispatched = true;     for (EventSubscriber wrapper : wrappers) {      enqueueEvent(event, wrapper);     }    }   } finally {    subscribersByTypeLock.readLock().unlock();   }  }  if (!dispatched && !(event instanceof DeadEvent)) {   post(new DeadEvent(this, event));  }  dispatchQueuedEvents(); }


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

@AutowiredEventListenerManager eventManager;//如下下保存商品的代码片段itemDao.save(product);eventManager.post(new SearchChangeEvent(product.getId));eventManager.post(new MailChangeEvent(product.getId));eventManager.post(new LogChangeEvent(product.getId));


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

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


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





深圳到英国旅游英国游报价英国旅游价格特价英国游深圳到英国旅游报价星期八小镇交通方便吗?广州星期八小镇有直达车吗? 岭南印象园特色是什么?广州岭南印象园怎么样? 岭南印象园在哪里?广州岭南印象园停车方便吗? 岭南印象园有什么?广州岭南印象园票价? 英国体型最小狗狗:身高11厘米比手机还小(图) 三亚旅行时有什么要注意的问题? 60岁高龄仙人掌刺破屋顶 长到7.6米高(图) 北京天坛恢复天圆地方格局 或展出编钟真品(图) 请问广州沙面如何去呀?沙面好玩吗? 五一期间中山岭南水乡有哪些活动景点呀? 岭南水乡的民风民俗有哪些特点和特色呢? 从广州坑口去白云山如何走? 2015深圳欢乐海岸跨年狂欢新亮点?欢乐海岸水秀表演新年烟花美吗? 深圳欢乐海岸2015新年狂欢几点开始?欢乐海岸水秀表演12月活动介绍? 2015深圳欢乐海岸圣诞元旦有什么活动?欢乐海岸水秀表演新年门票免费吗? 欢乐海岸2015新年狂欢活动介绍?深圳欢乐海岸水秀表演元旦有什么活动? AT84AD004BVEPW Datasheet AT84AD004BVEPW Datasheet AT84AD004BVTD Datasheet AT84AD004BVTD Datasheet AT84AD004CTD Datasheet AT84AD004CTD Datasheet 澳门一日游 澳门一日游 澳门一日游 上海风景图片 上海风景图片 上海风景图片 赣州美食 赣州美食 赣州美食