前言
Spring已经非常熟悉,Spring容器状态事件也是日常使用的功能,经常用于解耦,但是有时候事件却会重复的监听,此时就需要处理了,source也是有特殊用途的。
1. demo
构造一个Spring boot应用,写一个监听器。
@SpringBootApplication
public class EventMain {
public static void main(String[] args) {
ConfigurableApplicationContext currentContext = SpringApplication.run(EventMain.class, args);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.setParent(currentContext);
context.refresh();
}
}
@Component
public class SpringEventListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("hello--------------" + event.getSource());
}
}
启动后,果然监听2次。
2. 源码分析
Spring事件发送源码
public void publishEvent(ApplicationEvent event) {
publishEvent(event, null);
}
进一步定位
protected void publishEvent(Object event, @Nullable 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<>(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);
}
//这里很特殊,parent也会发送事件
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
parent也会发送当前容器的事件,如果parent还有parent,那么就变成大连串,如果监听器在parent定义,就会多次监听
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
//spring 通过Executor来处理同步还是异步
Executor executor = getTaskExecutor();
//获取监听器,for处理
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
同步异步执行代码一样,区别仅仅是是否线程池执行
是否定义错误处理,毕竟事件处理是可以自定义异常处理逻辑的
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 {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || matchesClassCastMessage(msg, event.getClass()) ||
(event instanceof PayloadApplicationEvent &&
matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception.
Log loggerToUse = this.lazyLogger;
if (loggerToUse == null) {
loggerToUse = LogFactory.getLog(getClass());
this.lazyLogger = loggerToUse;
}
if (loggerToUse.isTraceEnabled()) {
loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}
}
}
观察者模式,调取onApplicationEvent方法,源码分析结束。从原理分析是Spring会把事件向上传递,如果当前Spring容器有parent,比如Spring Cloud的Feign,那么事件就会重复消费。
3. 解决办法
如何解决其实并不是仅有一种方法,如果自定义事件,我们可以根据某些条件,比如当前容器是否有某个标记来解决,但是如果是Spring自己的事件或者我们不想定义标记,那么仅仅通过Spring容器也可以判断,一般而言:我们希望Spring事件仅在当前容器消费。那么仅需要==即可判断,所以建议把SpringApplicationContext放在source里,当然一般都会这样😄。
@Component
public class SpringEventListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private ApplicationContext applicationContext;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (applicationContext == event.getApplicationContext())
System.out.println("hello--------------" + event.getSource());
}
}
因为ApplicationContext是引用,内存地址是不变的,而且是单例,所以判断精确,而且仅需==即可。
总结
Spring事件向parent传递是Spring的特性,只是有时候我们消费事件并没有做幂等性,此时就需要通过各种手段规避这种问题,笔者提供一种比较简单的方式,代码也比较简单😋