Spring 事件源的用途

前言

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的特性,只是有时候我们消费事件并没有做幂等性,此时就需要通过各种手段规避这种问题,笔者提供一种比较简单的方式,代码也比较简单😋

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring是一个用于简化Java应用程序开发的框架。它使用基本的JavaBean来完成以前只能由EJB完成的任务,并提供了简单性、可测试性和松耦合性等优势。Spring用途不仅限于服务器端开发,几乎所有的Java应用程序都可以从Spring中受益。\[1\] 在使用Spring时,可以使用ApplicationContext或BeanFactory作为Spring的上下文。通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码造成的过度耦合。此外,Spring还提供了AOP编程的支持,方便进行面向切面编程。还有声明式事务的支持,可以通过声明式方式灵活地进行事务管理。此外,Spring还方便程序的测试,可以用非容器依赖的编程方式进行几乎所有的测试工作。同时,Spring还方便集成各种优秀框架,并降低JavaEE API的使用难度。此外,Spring代码设计精妙,结构清晰,是Java技术最佳实践的范例。\[3\] 总之,Spring框架提供了许多优势,包括简化开发、解耦、AOP编程、声明式事务、方便测试、集成各种优秀框架、降低JavaEE API的使用难度等。\[3\] #### 引用[.reference_title] - *1* [Java 必看的 Spring 知识汇总](https://blog.csdn.net/CRMEB/article/details/122208204)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [JavaSpring](https://blog.csdn.net/m0_62824239/article/details/125693312)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [【JavaSpring框架](https://blog.csdn.net/qq_43103529/article/details/115348059)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值