干货点
通过阅读该篇博客,你可以了解了解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<Class<IEvent>, List<MethodInfo>> eventListMap = new HashMap<>(); public static void addEventToMap(Class clz, Method method, Object obj) { List<MethodInfo> 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<MethodInfo> 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都监听到了监听到登录事件,证明该机制正常解决了单对多的行为触发问题。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对小牛知识库的支持。
我们的初创公司是使用Firebase和Google Cloud的“云原生”。我们正在研究事件驱动设计,但是我很难将这个概念与Firebase或GCP上的特定服务相匹配。 例如:用户通过移动应用程序创建合同草案(Firestore文档)。我们需要触发以下操作: 通过电子邮件和推送通知通知合同中的其他用户 写入通知表,以便用户在应用程序中有一个“收件箱”,并将其标记为已读或已删除 生成一个将在一个月内
本文向大家介绍wxPython事件驱动实例详解,包括了wxPython事件驱动实例详解的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了wxPython的事件驱动机制,分享给大家供大家参考。具体方法如下: 先来看看如下代码: 程序运行效果如下图所示: wxStaticText的两个构造函数官方文档如下: wxStaticText () Default constructor. wxSt
本文向大家介绍基于Angular.js实现的触摸滑动动画实例代码,包括了基于Angular.js实现的触摸滑动动画实例代码的使用技巧和注意事项,需要的朋友参考一下 先上图: 这个主要用到是angular-touch.js中封装好的ng-swipe-left,ng-swipe-right,向左或向右的触摸事件。结合css3的transition实现的动画。ng-class为切换写好的动画的class
本文向大家介绍基于vue实现swipe轮播组件实例代码,包括了基于vue实现swipe轮播组件实例代码的使用技巧和注意事项,需要的朋友参考一下 项目背景 图片轮播是前端项目必有项,当前有很多效果很酷炫的轮播插件,例如Swiper。 但是当项目中的图片轮播只需要一个很简单的轮播样式,比如这样的 我们引用这样一个110k的大插件,就大材小用了。再安利一下,swiper2.x和swiper3.x对移动和
本文向大家介绍基于python实现上传文件到OSS代码实例,包括了基于python实现上传文件到OSS代码实例的使用技巧和注意事项,需要的朋友参考一下 基础环境 小文件上传 分片上传 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持呐喊教程。
本文向大家介绍Python基于Tkinter实现的记事本实例,包括了Python基于Tkinter实现的记事本实例的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了Python基于Tkinter实现的记事本。分享给大家供大家参考。具体如下: 希望本文所述对大家的Python程序设计有所帮助。