SpringEvent 应用、源码分析、存在的坑

目录

SpringEvent使用

SpringEvent源码分析

存在的坑


 

SpringEvent使用

开发中常会遇到需要解耦的业务,比较典型的场景是电商系统订单结算完成通知商品服务减库存。结算的成功与否也不依赖库存扣减结果,扣减库存不需要实时完成(秒杀除外)。分布式场景可以使用消息队列方案,单机应用可以起一个线程调用扣减接口,但是线程调用较难维护,因为新增一个下游节点就要在代码中加一个调用接口,不优雅。比较起来SpringEvent消息通知机制可以很好的处理单机场景。

SpringEvent的使用十分简单,核心内容为创建继承了ApplicationEvent的事件、调用ApplicationEventPublisher发送事件、接收者实现ApplicationListener方法接收事件。使用分为以下三个步骤:

声明事件(继承ApplicationEvent类)

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

发送事件(调用ApplicationEventPublisher的publishEvent方法发送事件)

@Autowired
private ApplicationEventPublisher publisher;
@Override
public void register(User user) throws Exception {
    publisher.publishEvent(new UserRegisterEvent(user));
}

监听器接收事件(新建监听类,实现ApplicationListener接口,实现onApplicationEvent方法)

@Async
public class CouponListener implements ApplicationListener<UserRegisterEvent> {
    @Override
    public void onApplicationEvent(UserRegisterEvent event) {
        System.out.println(event.getUser());
    }
}

在启动类中加入@EnableAsync注解

SpringEvent源码分析

读源码是一个枯燥的过程,源码解析的图书、教程也很难理解,不管读者的基础如何,开头就介绍十几个命名相似、方法名贼拉长的类,然后A调B,B调C,C调E,E再调A,A又调C.....,巴拉巴拉一大堆调用,我往往翻了两三页就忘了把前面的调用关系都忘了,也没搞懂为什么要这么调。

其实学习框架源码时可以换个思路,我们可以把源码当成是一段没汉语注解的、写的比较优秀的代码,和我们日常写的业务代码从本质上没有区别。

既然框架源码和普通代码没区别,为什么那么难懂呢?

回想一下,我们新加入一个公司熟悉项目的时候会把class文件反编译出来挨个阅读吗?不会,那我们是怎么做的呢?
先熟悉整个项目,了解项目中用到什么技术、登陆系统操作一下功能,再深入各个模块,了解各自模块实现了什么业务,对接了哪些接口等,最后深入阅读代码。按着这样的套路一遍走下来就能轻松熟悉整个项目。

在分析框架源码前我们也可以先对使用场景、使用的目的、调用方法整体进行整体把握。思考源码底层要实现什么业务,可能会用什么技术(比如反射、切面、线程等)去实现,最后思考假如不用框架自己会怎么实现。

所以学习SpringEvent时我们可以套一下这个方法。我们使用SpringEvent的流程就是上游发出消息,下游接受消息。转化为代码具体的实现是调用了applicationEventPublisher的publishEvent方法,传入了UserRegisterEvent这个事件,触发onApplicationEvent。

读源码前可以大胆提几个猜测:

  • UserRegisterEvent的作用是为了辨识推送者和接受者,因为在调用前并不知道接收方是谁
  • 在运行中动态调用接受者可能是使用了反射机制。

带着这些猜测在源码中验证吧!

public void publishEvent(ApplicationEvent event) {
		publishEvent(event, null);
}

protected void publishEvent(Object event, ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");
		
		// Decorate event as an ApplicationEvent if necessary
		ApplicationEvent applicationEvent;
		if (event instanceof ApplicationEvent) {
                //鉴定事件类型,向上造型
			applicationEvent = (ApplicationEvent) event;
		}
		else {
			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 {
                    //调用广播方法,发送事件
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

}
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);
			}
		}
	}
//通过事件类型和事件体获取说有监听者
protected Collection<ApplicationListener<?>> getApplicationListeners(
			ApplicationEvent event, ResolvableType eventType) {

		Object source = event.getSource();
		Class<?> sourceType = (source != null ? source.getClass() : null);
		ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

		// Quick check for existing entry on ConcurrentHashMap...
		ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
		if (retriever != null) {
			return retriever.getApplicationListeners();
		}
}

 

 

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
		ErrorHandler errorHandler = getErrorHandler();
		if (errorHandler != null) {
			try {
				doInvokeListener(listener, event);
			}
			catch (Throwable err) {
				errorHandler.handleError(err);
			}
		}
		else {
			doInvokeListener(listener, event);
		}
	}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
            //调用监听者的onApplicationEvent方法,就是我们写的CouponListener类的方法
			listener.onApplicationEvent(event);
		}
		catch (ClassCastException ex) {
			String msg = ex.getMessage();
			if (msg == null || matchesClassCastMessage(msg, event.getClass().getName())) {
				// Possibly a lambda-defined listener which we could not resolve the generic event type for
				// -> let's suppress the exception and just log a debug message.
				Log logger = LogFactory.getLog(getClass());
				if (logger.isDebugEnabled()) {
					logger.debug("Non-matching event type for listener: " + listener, ex);
				}
			}
			else {
				throw ex;
			}
		}
	}

SpringEvent的实现方法中用到了AbstractApplicationContext、SimpleApplicationEventMulticaster、AbstractApplicationEventMulticaste等底层公用类的方法,本文不深究方法中每个判断的用途,只是验证SpringEvent的底层实现机理。

存在的坑

第一:项目中使用SpringEvent异步模式时,要考虑清楚业务是不是真正允许异步。因为它在高压下的延迟程度会超过我们的预期,在开发环境中秒达的事件,在线上环境可能会延迟好几分钟。比如扣减库存的场景,原本设定在用户下单后就立刻发送springEvent事件通知下游减库存,如果下游库存系统在5分钟后才收到这条事件,必然会发生超卖的情况。

第二:如果上游发出的消息体中包含操作时间、操作状态等关键信息,下游最好做一下校验,不然在延迟送达的过程中状态可能已经发生了更新,数据可能不准确。

第三:多个有顺序的状态更新场景也要慎重使用,事件的实际送达顺序可能和投递顺序不一致,因为异步事件底层是多线程。

参考资料:

Spring Event事件通知机制 源码学习

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值