spring 中的事件机制

使用

spring 默认提供的系统事件

一般当容器启动后,我们需要加载某些资源或者执行操作,可以通过 ContextRefreshedEvent 完成。

示例:

public class ApplicationStartupListener implements ApplicationListener<ContextStartedEvent> {

	@Override
	public void onApplicationEvent(ContextStartedEvent event) {
		// 可以拿到容器
		ApplicationContext applicationContext = event.getApplicationContext();
		// 在这里进行业务处理
	}

}

启动时预先注册进容器:

AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext();
app.register(Config8.class);
app.addApplicationListener(new ApplicationStartupListener()); //注意这里
app.refresh();
app.start();
app.close();

non-webspring 中注册可以采用这种方式,方便让开发者清楚的知道现在有哪些事件监听器是起作用的。实际上,只需要将事件监听器注册进 spring 容器即可生效。于此同时可以发现 EventObject 有非常多的事件实现,具体使用时可以查看有没有符合心意的。

自定义实现
实现接口

如上,我们看到名为 PayloadApplicationEvent<T> 的事件接口,可以利用此接口来实现自己的自定义事件。

public class MessageListener implements ApplicationListener<PayloadApplicationEvent<Message>> {

	@Override
	public void onApplicationEvent(PayloadApplicationEvent<Message> event) {
		System.out.println("接收到事件:" + event.getPayload().getMessage());
	}

}

发布事件:

AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext();
app.register(Config8.class);
app.addApplicationListener(new MessageListener());
app.refresh();
app.start();

app.publishEvent(new Message("消息"));
app.close();

这里也是同样的道理,只需要注册进容器即可。

使用 @EventListener 注解

上面的用法是实现了接口,spring 也提供了注解的支持。

@Service
public class EmailService {

	@EventListener
	public void send(String address) {
		System.out.println("发送邮件 -> " + address);
	}
}

AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext();
app.register(Config8.class);
app.addApplicationListener(new ApplicationStartupListener());
app.refresh();
app.start();
app.publishEvent("pleuvior@foxmail.com");
app.close();

源码分析

  1. 初始化事件分发器

新建一个简单的事件广播器 SimpleApplicationEventMulticaster ,注意此处的 taskExecutornull

这里我们当然也可以自己制造一个。

@Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
public ApplicationEventMulticaster initApplicationEventMulticaster(BeanFactory beanFactory) {
	
	SimpleApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);

	ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("事件处理线程-%d").build();
	ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors() * 2,
			200, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(128), threadFactory);
	
	//threadPoolExecutor.prestartAllCoreThreads();
	applicationEventMulticaster.setTaskExecutor(threadPoolExecutor);
	
	applicationEventMulticaster.setErrorHandler(new ErrorHandler() {
		
		@Override
		public void handleError(Throwable t) {
				System.out.println("出错了");
				t.printStackTrace();
		}
	});
	return applicationEventMulticaster;
}
  1. 注册监听器

先将静态的监听器注册进来,即实现了 ApplicationListener 接口的。

  1. 容器启动

可以看到 startstop 时分别发布了两个事件。点进去看到:

当我们发布的内容没有实现 ApplicationEvent 接口,则创建一个 PayloadApplicationEvent 类型的事件。

如下标红的两处,即是找到了对应的监听器。其中第一处的 defaultRetrieve.applicationListeners的值是下图执行时添加的。可以注意到,这里的事件类型匹配是根据参数类型,所以监听器一旦出现多个形参是基本类型的方法,会发现这些事件都会被广播一次。

下图是给每个被 @EventListener 标记的方法创建一个新的事件监听器,并添加到广播器中:

这里 supportEvent 方法点进去看,会发现有判断当前监听器的类型,当我们的监听器是使用注解创建时类型为 ApplicationListenerMethodAdapter,实现接口的则为 GenericApplicationListenerAdapter。 这两种判断有些区别,如果是 GenericApplicationListenerAdapter 会去检查当前发布的事件对象是否为监听器的实现,它里面不会去判断 payload 的继承关系。而 ApplicationListenerMethodAdapter 则先判断是否为实现(基本上不会),它会再次判断 payload 的继承关系。这样就会出现子类继承父类,发布父类事件,子类父类事件都执行的问题。

发布事件的代码

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		Executor executor = getTaskExecutor();
		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);
		}
	}

总结

至此,整个流程已经分析完毕。使用默认的事件机制,可以实现编译期的解耦,但是不能实现运行时解耦。所以可以提供线程池,让广播时新启动线程,这样则达到了单应用中合理的解耦。如果项目中某行代码不想在声明式事务中执行,则可以使用此种方式。而它也比编程时事务的代码复杂度以及合理性更胜一筹。建议使用实现接口的形式,这样不同的业务代码是隔离的。并且不会出现父子通知的问题。

代码: spring 事件

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值