Java Spring 事件监听详情解析
作者:??下岗码农大飞???? 发布时间:2021-06-05 00:02:08
前言
前段时间因为工作的需要用到Spring事件,翻翻文档将功能实现了,但是存在少许理解不畅的地方,今天有空来梳理梳理。
需求背景
叶子同学在新入职公司,老大让他实现登陆功能,叶子随手写完,上线无bug,一切安好
//登陆伪代码
public void login(....){
userLogin(....);
}
几天之后,老大说为维护用户的粘度,每天登陆送积分。叶子同学,二话不说,一顿操作后,上线无bug,一切安好
//登陆伪代码
public void login(....){
//登陆
userLogin(....);
//送积分
loginPoint(....)
}
又几天后,老大说,为了客户安全,每次异地登陆发送邮件。叶子同学稍微抱怨,看在钱份上又是一顿操作后,上线无bug, 一切安好
//登陆伪代码
public void login(....){
//登陆
userLogin(....);
//送积分
loginPoint(....)
//发送邮件
sendEmail(....)
}
又又几天后,老大说,部分客户不用邮件,用短信。叶子同学压着怒气,看着银行卡,又是一顿操作后,上线无bug, 一切安好
//登陆伪代码
public void login(....){
//登陆
userLogin(....);
//送积分
loginPoint(....)
//发送邮件
sendEmail(....)
//发短信
sendSms(...)
}
又又又几天后,老大还没开口,叶子同学就忍无可忍啦,得加钱。老大哄了好久,说不改需求了。改bug,用户抱怨登陆慢,有时还不成功。叶子二话不说,接手排查,查出问题啦
1> 邮件发送耗时
2>同步实现,如果邮件发送超时,登陆会出异常
代码改进:
//登陆伪代码
public void login(....){
//登陆
userLogin(....);
try{
//送积分
new Thread(()->loginPoint(....)).start();
//发送邮件
new Thread(()->sendEmail(....)).start();
//发短信
new Thread(()->sendSms(....)).start();
}catch(Exception e){
//异常处理
}
}
问题解决,功能实现,ok~
又又又又几天之后,老大说,只需要实现登陆功能即可,其他都不要~
叶子同学一句 * ,然后是一段含母非常高的国粹。
此时,问:如果你是叶子同学,你有啥方案能优雅应对上面的需求变更呢?
一种优雅方案:事件监听机制
事件概念
定义
事件监听机制:就是对一个事件(行为动作)进行监听,当外界触发某事件时,监听程序马上被捕获该事件,并触发相应的响应,这过程称之为事件监听机制。
组成
事件监听机制有3个核心组成部分:
1>事件,标记某种行为动作,比如:鼠标点击事件,鼠标移动事件等。
2>事件源,被监控的对象或组件,事件发生的地方。比如:点击按钮,触发点击事件,按钮就是实现源。
3>事件 * ,监听事件的操作类,一旦事件发生(被触发),则执行事件 * 预设的逻辑,进行事件响应。
1>定义事件,并绑定到事件源中
2>定义事件 * ,监听事件源
3>用户某行为触发事件
4>事件 * 监控到事件发送,执行事件响应逻辑。
以上面的登录为例:
事件源:login方法
事件:用户login行为
事件 * :此处没有,需要额外定制,但是事件响应:送积分,发邮件,发短信。
事件实现
以登录为例子实现事件监听机制
1>定义抽象事件
/**
* 抽象事件类
* 作用:定制事件逻辑
*/
public class AbstractEvent {
//绑定的事件源
private Object source;
public AbstractEvent(Object source) {
this.source = source;
}
public Object getSource() {
return source;
}
public void setSource(Object source) {
this.source = source;
}
}
2>定制登陆事件
/**
* 登陆事件
*/
public class LoginEvent extends AbstractEvent{
public LoginEvent(Object source) {
super(source);
}
}
3>定义事件 *
/**
* 事件 *
* 作用:当监控的事件发送时,执行预设的逻辑
*/
public interface EventListener<E extends AbstractEvent> {
/**
* 预设逻辑方法
* 事件被触发,马上执行
*/
void onEvent(E event);
}
4>定制登陆事件 *
积分 *
/**
* 加积分 * 器:
* 当用户登陆事件触发后,马上执行
*/
public class PointsListener implements EventListener<LoginEvent> {
public void onEvent(LoginEvent event) {
System.out.println(event.getSource() + "发生后,执行积分+1操作");
System.out.println(Thread.currentThread().getName());
}
}
短信 *
/**
* 加积分 * 器:
* 当用户登陆事件触发后,马上执行
*/
public class SmsListener implements EventListener<LoginEvent> {
public void onEvent(LoginEvent event) {
System.out.println(event.getSource() + "发生后,执行发送短信操作");
System.out.println(Thread.currentThread().getName());
}
}
邮件 *
/**
* 加积分 * 器:
* 当用户登陆事件触发后,马上执行
*/
public class EmailListener implements EventListener<LoginEvent> {
public void onEvent(LoginEvent event) {
try {
//模拟10s延时
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(event.getSource() + "发生后,执行发送邮件操作");
System.out.println(Thread.currentThread().getName());
}
}
5>定义事件广播器
/**
* 事件广播器
* 1>注册事件 *
* 2>删除事件 *
* 3>事件触发时,广播事件
*/
public interface EventMulticaster {
//广播事件
void multicastEvent(AbstractEvent event);
//注册事件 *
void registListener(EventListener listener);
//删除事件 *
void removeListener(EventListener listener);
}
6>定制简单的事件广播器
/**
* 事件广播器实现类
* 作用:维护事件 *
*/
public class SimpleEventMulticaster implements EventMulticaster {
//key:事件字节码对象, value:当前事件绑定的事件 *
private Map<Class<?>, List<EventListener>> map = new HashMap<Class<?>, List<EventListener>>();
public void multicastEvent(AbstractEvent event) {
List<EventListener> eventListeners = map.get(event.getClass());
if(eventListeners != null){
ExecutorService executorService = Executors.newCachedThreadPool();
for (EventListener eventListener : eventListeners) {
//异步
executorService.submit(()-> eventListener.onEvent(event));
//同步
//eventListener.onEvent(event);
}
executorService.shutdown();
}
}
public void registListener(EventListener listener) {
//获取 * 绑定的事件
ParameterizedType getType = (ParameterizedType)listener.getClass().getGenericInterfaces()[0];
Type type = getType.getActualTypeArguments()[0];
Class<?> clz = (Class<?>) type;
List<EventListener> listeners = map.get(clz);
if(listeners == null){
listeners = new ArrayList<EventListener>();
map.put(clz, listeners);
}
listeners.add(listener);
}
public void removeListener(EventListener listener) {
//获取 * 绑定的事件
ParameterizedType getType = (ParameterizedType)listener.getClass().getGenericInterfaces()[0];
Type type = getType.getActualTypeArguments()[0];
Class<?> clz = (Class<?>) type;
List<EventListener> listeners = map.get(clz);
if(listener != null){
listeners.remove(listener);
}
}
}
7>综合测试
public class App {
//1:初始化事件广播器
public static SimpleEventMulticaster multicaster = new SimpleEventMulticaster();
static {
//2:注册 *
//登陆事件上绑定3个 *
multicaster.registListener(new PointsListener());
multicaster.registListener(new EmailListener());
multicaster.registListener(new SmsListener());
}
//3:模拟登陆
public static void login(){
//4:用户登陆成功触发登陆事件
System.out.println("用户执行登陆逻辑");
System.out.println(Thread.currentThread().getName());
//5:广播登陆事件
multicaster.multicastEvent(new LoginEvent("用户登陆啦"));
System.out.println("登陆成功.....");
}
public static void main(String[] args) {
App.login();
System.out.println(Thread.currentThread().getName());
}
}
时序图
来源:https://juejin.cn/post/7114669073320902669


猜你喜欢
- 目录前言项目环境1.线程池示例2.shutdown3.isShutdown4.isTerminated5.awaitTermination6
- 推荐阅读idea官网下载链接(对应版本号下载):https://www.jetbrains.com/idea/download/other.
- 只要是开发和手机通讯录有关的应用,总要学会获取联系人信息,每次都google很麻烦,怎么办?写一个工具类,获取到通讯录里所有的信息并分好类,
- 前言相信大家对Android悬浮窗应该是很熟悉了,比如说腾讯视频、爱奇艺等APP都有悬浮窗功能。在你打游戏的同时还可以看视频,充分利用屏幕空
- Android 设置颜色的方法总结Android中有几种设置界面背景及文字颜色的方法,下面由浅入深分别介绍Android中设置颜色的几种方法
- 新建项目IDEA上方工具栏点击:文件->新建->模块此时的目录结构:需要在main文件夹下补全两个文件夹,点击main,右键-&
- 现在的手机一般都会提供相机功能,有些相机的镜头甚至支持1300万以上像素,有些甚至支持独立对焦、光学变焦这些只有单反才有的功能,甚至有些手机
- 在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师
- 前言通过前面的博客我们已经大致了解了关于Java的基本知识,而下面的几篇博客我们着重开始对于数据结构的知识进行学习,这篇博客我们就了解关于顺
- 1.pom文件导入依赖<!-- kafka --><dependency> <groupId>
- Lambda是第十一个希腊字母,大写Λ,小写λ,额,跑题了…Lambda表达式 是Java8的新特性之一:Lambda表达式函数式接口流AP
- 研发背景公司安全部目前针对内部系统的网络访问日志的安全审计,大部分都是T+1时效,每日当天,启动Python编写的定时任务,完成昨日的日志审
- 本文实例为大家分享了Unity封装延时调用定时器的具体代码,供大家参考,具体内容如下封装一个延时调用定时器类using System.Col
- 上代码喽~package ncu.com.app.chatpter_5;import java.util.Random;//结点类class
- 一、Arthas官方文档https://arthas.aliyun.com/doc/二、springBoot整合方式1、pom文件引入<
- SpringBoot2.x过后static下的静态资源无法访问package com.example.thymeleaf.commons;i
- 一、项目简述功能: 前后用户的登录注册,婚纱照片分类,查看,摄影师预 订,后台订单管理,图片管理等等。二、项目运行环境配置: Jdk1.8
- 简介今天的课程开始进入高级课程类了,我们要开始接触网络协议、设备等领域编程了。在今天的课程里我们会使用OKHttp组件来访问网络资源而不是使
- JsonTools.java package com.lihua.json.tools;import net.sf.json.JSONObj
- 本文为大家分享了C#利用VS中插件打包并发布winfrom程序,供大家参考,具体内容如下1.先在VS 的扩展更新中搜索此插件【2015 in