使用Spring Event解耦业务开发

摘要: 使用Spring Event解耦业务开发

正文:

使用Spring Event解耦业务开发

事件驱动

事件驱动模型通常被理解为观察者模式或者发布-订阅模型

Spring 事件是观察者模式的一种体现,对象间的一对多关系,被观察者发出信号时候会通知监听该事件的观察者;而发布-订阅模型往往需要一个调度中心,如消息队列等

业务场景

  • 用户注册
  • 发短信/确认邮件
  • 送新人优惠券
  • 送抽奖
  • ….

伪代码

主流程

//注册
userMapper.saveUser(user);
//发确认邮件
sendEmail(String email);
//发短信
sendMessage(String mobile);
//送优惠券
sendCoupon(String userId);
//送抽奖机会
sendLottery(String userId);
//各种活动...
send...();

尽管我们方法抽象的很好,但是当这种事件(注册后续操作)越来越多时,主方法就会显得很乱,并且随着业务需求的变化,这个维护起来也很麻烦

改进版

主流程

//注册
userMapper.saveUser(user);
//发送注册成功事件
publisher.publishEvent(new UserRegisterEvent(user));

注册事件

class UserRegisterEvent extends ApplicationEvent {
    private static final long serialVersionUID = -4829855648590354032L;
    
    public UserRegisterEvent(User user) {
        super(user);
    }
    
    public User getUser() {
        return (User) source;
    }
}

不同监听

//发短信监听
class MessageListener implements ApplicationListener<UserRegisterEvent> {
    @Override
    public void onApplicationEvent(UserRegisterEvent event) {
    	sendMessage(event.getUser().getMobile());
    }
}
//发优惠券监听
class CouponListener implements ApplicationListener<UserRegisterEvent> {
    @Override
    public void onApplicationEvent(UserRegisterEvent event) {
    	sendCoupon(event.getUser().getId());
    }
}
//...监听

同步 OR 异步

Spring 事件既可以同步又可以异步,对于重要的业务最好采用同步方式,对于不重要的或不希望其阻塞主线程从而导致响应变慢可以采用异步方式

同步(default)

监听会加入到主线程的事务中,可以通过Order来调整bean装配的优先级来实现监听的执行顺序

异步

需要配置线程池来实现,顺序无法保证

综上所述,Spring 事件主要还是对代码层面的解耦

Spring Event 实现细节

Source: 4.2

publisher.publishEvent() 为入口

org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)

protected void publishEvent(Object event, ResolvableType eventType) {
    //如果是ApplicationEvent类型对象
    if (event instanceof ApplicationEvent类型对象) {
        applicationEvent = (ApplicationEvent) event;
    }
    else {
        //否则就new一个PayloadApplicationEvent
        applicationEvent = new PayloadApplicationEvent<Object>(this, event);
        if (eventType == null) {
            eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
        }
    }

    // Multicast right now if possible - or lazily once the multicaster is initialized
    if (this.earlyApplicationEvents != null) {
        this.earlyApplicationEvents.add(applicationEvent);
    }
    else {
        //实际执行是委托给ApplicationEventMulticaster
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    }
}

同步or 异步

org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)

@Override
	public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			Executor executor = getTaskExecutor();
			if (executor != null) {
				executor.execute(new Runnable() {
					@Override
					public void run() {
						invokeListener(listener, event);
					}
				});
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

初始化

org.springframework.context.support.AbstractApplicationContext#refresh

@Override
public void refresh() throws BeansException, IllegalStateException {
    ...
    initApplicationEventMulticaster();
    ...
}

org.springframework.context.support.AbstractApplicationContext#initApplicationEventMulticaster

protected void initApplicationEventMulticaster() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
        //手动实现了名称为applicationEventMulticaster的bean
    }
    else {
    	//否则就默认自定义一个名称为SimpleApplicationEventMulticaster的监听器容器
    }
}

Demo

Spring-Event-Demo

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏FreeBuf

远程RPC溢出EXP编写实战之MS06-040

0x01 前言 MS06-040算是个比较老的洞了,在当年影响十分之广,基本上Microsoft大部分操作系统都受到了影响,威力不亚于17年爆出的”永恒之蓝”漏...

30410
来自专栏Java帮帮-微信公众号-技术文章全总结

Web-第二十三天 Web商城实战三【悟空教程】

<a href="${pageContext.request.contextPath}/OrderServlet?method=findByUid">我的订单<...

1821
来自专栏Golang语言社区

写在学习golang一个月后

连接池。由于PHP没有连接池,当高并发时就会有大量的数据库连接直接冲击到MySQL上,最终导致数据库挂掉。虽然Swoole有连接池,但是Swoole只是PHP的...

2712
来自专栏比原链

Derek解读Bytom源码-P2P网络 地址簿

Gitee地址:https://gitee.com/BytomBlockchain/bytom

1143
来自专栏SDNLAB

Ryu:如何在LLDP中添加自定义LLDPDU

在许多实验场景中,都需要使用链路发现协议(LLDP)来发现链路,从而构建网络拓扑。然而LLDP协议不仅仅可以用来发现拓扑,也可以用于时延检测等业务。LLDP通过...

4116
来自专栏挖掘大数据

Eclipse连接Hadoop集群和WordCount实战

本文将主要介绍Eclipse连接Hadoop集群和WordCount实践项目两大内容。

3040
来自专栏影子

springboot添加多数据源连接池并配置Mybatis

5023
来自专栏辣子鸡的技术分享

Mybatis自动代码生成器的实现

原博地址https://laboo.top/2018/11/26/a-db/#more

1256
来自专栏Jerry的SAP技术分享

Opportunity的chance of success的赋值逻辑

该字段的值和另外两个字段Sales Stage和Status都相关。

2508
来自专栏xingoo, 一个梦想做发明家的程序员

开启服务和停止服务

Start函数用于开启服务 1 初始化状态变量 2 创建监听套接字 3 加载使用扩展API函数 4 创建完成端口对象 5 建立监听套接字和完成端口对象间的关联 ...

2818

扫码关注云+社区

领取腾讯云代金券