热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

基于spring如何实现事件驱动实例代码

这篇文章主要给大家介绍了关于基于spring如何实现事件驱动的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用spring具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

干货点

通过阅读该篇博客,你可以了解了解java的反射机制、可以了解如何基于spring生命周期使用自定义注解解决日常研发问题。具体源码可以点击链接。

问题描述

在日常研发中,经常会遇见业务A的某个action被触发后,同时触发业务B的action的行为,这种单对单的形式可以直接在业务A的action执行结束后直接调用业务B的action,那么如果是单对多的情况呢?

方案解决

这里提供一种在日常研发中经常使用到的机制,基于spring实现的事件驱动,即在业务A的action执行完,抛出一个事件,而业务B、C、D等监听到该事件后处理相应的业务。

场景范例

这里提供一个场景范例,该范例基于springboot空壳项目实现,具体可以查看源码,此处只梳理关键步骤。

步骤一:

定义一个注解,标志接收事件的注解,即所有使用了该注解的函数都会在对应事件被抛出的时候被调用,该注解实现比较简单,代码如下

/**
 * @author xifanxiaxue
 * @date 3/31/19
 * @desc 接收事件的注解
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ReceiveAnno {

 // 监听的事件
 Class clz();
}

如果想了解注解多个参数的意义是什么的可以点击链接查看博主之前写过文章。

定义事件接口

/**
 * @author xifanxiaxue
 * @date 3/31/19
 * @desc
 */
public interface IEvent {
}

所有事件都需要实现该接口,主要是为了后面泛型和类型识别。

定义MethodInfo

/**
 * @author xifanxiaxue
 * @date 3/31/19
 * @desc
 */
public class MethodInfo {

 public Object obj;
 public Method method;

 public static MethodInfo valueOf(Method method, Object obj) {

 MethodInfo info = new MethodInfo();
 info.method = method;
 info.obj = obj;
 return info;
 }

 public Object getObj() {
 return obj;
 }

 public Method getMethod() {
 return method;
 }
}

该类只是做了Object和Method的封装,没有其他作用。

步骤二:

实现一个事件容器,该容器的作用是存放各个事件以及需要触发的各个业务的method的对应关系。

/**
 * @author xifanxiaxue
 * @date 3/31/19
 * @desc 事件容器
 */
public class EventContainer {

 private static Map, List> eventListMap = new HashMap<>();

 public static void addEventToMap(Class clz, Method method, Object obj) {

 List methodInfos = eventListMap.get(clz);
 if (methodInfos == null) {
 methodInfos = new ArrayList<>();
 eventListMap.put(clz, methodInfos);
 }

 methodInfos.add(MethodInfo.valueOf(method, obj));
 }

 public static void submit(Class clz) {

 List methodInfos = eventListMap.get(clz);
 if (methodInfos == null) {
 return;
 }

 for (MethodInfo methodInfo : methodInfos) {
 Method method = methodInfo.getMethod();
 try {
 method.setAccessible(true);
 method.invoke(methodInfo.getObj());
 } catch (IllegalAccessException e) {
 e.printStackTrace();
 } catch (InvocationTargetException e) {
 e.printStackTrace();
 }
 }
 }
}

其中的addEventToMap函数的作用是将对应的事件、事件触发后需要触发的对应业务内的Method存放在eventListMap内;而submit函数会在其他业务类内抛出事件的时候被调用,而作用是从eventListMap中取出对应的Method,并通过反射触发。

步骤三:

实现事件处理器,该事件处理器的作用是在bean被spring容器实例化后去判断对应的bean是否有相应函数加了@ReceiveAnno注解,如果有则从中取出对应的Event并放入EventContainer中。

/**
 * @author xifanxiaxue
 * @date 3/31/19
 * @desc 事件处理器
 */
@Component
public class EventProcessor extends InstantiationAwareBeanPostProcessorAdapter {

 @Override
 public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {

 ReflectionUtils.doWithLocalMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
 @Override
 public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {

 ReceiveAnno anno = method.getAnnotation(ReceiveAnno.class);
 if (anno == null) {
  return;
 }

 Class clz = anno.clz();
 try {
  if (!IEvent.class.isInstance(clz.newInstance())) {
  FormattingTuple message = MessageFormatter.format("{}没有实现IEvent接口", clz);
  throw new RuntimeException(message.getMessage());
  }
 } catch (InstantiationException e) {
  e.printStackTrace();
 }

 EventContainer.addEventToMap(clz, method, bean);
 }
 });

 return super.postProcessAfterInstantiation(bean, beanName);
 }

}

关于InstantiationAwareBeanPostProcessorAdapter的描述,有需要的可以查看我之前的文章,其中比较详细描述到Spring中的InstantiationAwareBeanPostProcessor类的作用。

步骤四:

对应的业务类的实现如下:

/**
 * @author xifanxiaxue
 * @date 3/31/19
 * @desc
 */
@Slf4j
@Service
public class AFuncService implements IAFuncService {

 @Override
 public void login() {
 log.info("[{}]抛出登录事件 ... ", this.getClass());
 EventContainer.submit(LoginEvent.class);
 }
}

A业务类,login会在被调用的生活抛出LoginEvent事件。

/**
 * @author xifanxiaxue
 * @date 3/31/19
 * @desc
 */
@Service
@Slf4j
public class BFuncService implements IBFuncService {

 @ReceiveAnno(clz = LoginEvent.class)
 private void doAfterLogin() {
 log.info("[{}]监听到登录事件 ... ", this.getClass());
 }
}
/**
 * @author xifanxiaxue
 * @date 3/31/19
 * @desc
 */
@Service
@Slf4j
public class CFuncService implements ICFuncService {

 @ReceiveAnno(clz = LoginEvent.class)
 private void doAfterLogin() {
 log.info("[{}]监听到登录事件 ... ", this.getClass());
 }
}

B和C业务类的doAfterLogin都分别加了注解 @ReceiveAnno(clz = LoginEvent.class) ,在监听到事件LoginEvent后被触发。
为了触发方便,我在spring提供的测试类内加了实现,代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class EventMechanismApplicationTests {

 @Autowired
 private AFuncService aFuncService;

 @Test
 public void contextLoads() {
 aFuncService.login();
 }
}

可以从中看出启动该测试类后,会调用业务A的login函数,而我们要的效果是B业务类和C业务类的doAfterLogin函数会被自动触发,那么结果如何呢?

结果打印

我们可以从结果打印中看到,在业务类A的login函数触发后,业务类B和业务类C都监听到了监听到登录事件,证明该机制正常解决了单对多的行为触发问题。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。


推荐阅读
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • 本文详细介绍了如何使用Spring Boot进行高效开发,涵盖了配置、实例化容器以及核心注解的使用方法。 ... [详细]
  • 本文详细探讨了Java中StringBuffer类在不同情况下的扩容规则,包括空参构造、带初始字符串和指定初始容量的构造方法。通过实例代码和理论分析,帮助读者更好地理解StringBuffer的内部工作原理。 ... [详细]
  • 本文探讨了领域驱动设计(DDD)的核心概念、应用场景及其实现方式,详细介绍了其在企业级软件开发中的优势和挑战。通过对比事务脚本与领域模型,展示了DDD如何提升系统的可维护性和扩展性。 ... [详细]
  • 深入了解 Windows 窗体中的 SplitContainer 控件
    SplitContainer 控件是 Windows 窗体中的一种复合控件,由两个可调整大小的面板和一个可移动的拆分条组成。本文将详细介绍其功能、属性以及如何通过编程方式创建复杂的用户界面。 ... [详细]
  • 实体映射最强工具类:MapStruct真香 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 本文探讨了在Linux系统上使用Docker时,通过volume将主机上的HTML5文件挂载到容器内部指定目录时遇到的403错误,并提供了解决方案和详细的操作步骤。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 作为一名专业的Web前端工程师,掌握HTML和CSS的命名规范是至关重要的。良好的命名习惯不仅有助于提高代码的可读性和维护性,还能促进团队协作。本文将详细介绍Web前端开发中常用的HTML和CSS命名规范,并提供实用的建议。 ... [详细]
  • 本文探讨了在 ASP.NET MVC 5 中实现松耦合组件的方法。通过分离关注点,应用程序的各个组件可以更加独立且易于维护和测试。文中详细介绍了依赖项注入(DI)及其在实现松耦合中的作用。 ... [详细]
  • Startup 类配置服务和应用的请求管道。Startup类ASP.NETCore应用使用 Startup 类,按照约定命名为 Startup。 Startup 类:可选择性地包括 ... [详细]
  • 网易严选Java开发面试:MySQL索引深度解析
    本文详细记录了网易严选Java开发岗位的面试经验,特别针对MySQL索引相关的技术问题进行了深入探讨。通过本文,读者可以了解面试官常问的索引问题及其背后的原理。 ... [详细]
  • 自己用过的一些比较有用的css3新属性【HTML】
    web前端|html教程自己用过的一些比较用的css3新属性web前端-html教程css3刚推出不久,虽然大多数的css3属性在很多流行的浏览器中不支持,但我个人觉得还是要尽量开 ... [详细]
  • 本文将深入探讨如何在不依赖第三方库的情况下,使用 React 处理表单输入和验证。我们将介绍一种高效且灵活的方法,涵盖表单提交、输入验证及错误处理等关键功能。 ... [详细]
author-avatar
陈亮跑街向_114
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有