Spring 学习 之 再探publish-event机制
之前的文章链接:https://blog.csdn.net/qq_41907991/article/details/88544777
我们要知道的是,Spring的publish-event使用的是监听者模式
监听者模式包含了一个监听者Listener与之对应的事件Event,还有一个事件发布者EventPublish,过程就是EventPublish发布一个事件,被监听者捕获到,然后执行事件相应的方法
监听者模式跟观察者模式的区别:
观察者模式是一对多的模式,一个被观察者Observable和多个观察者Observer,被观察者中存储了所有的观察者对象,当被观察者接收到一个外界的消息,就会遍历广播推算消息给所有的观察者
- 首先我们看下两个发布事件的方法,可以发现,最后都会调到
void publishEvent(Object event)
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
继续跟踪代码:
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null"); // 如果是一个ApplicationEvent,就强转
// 如果不是的话,包装成一个PayloadApplicationEvent
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
// 包装成一个PayloadApplicationEvent,并获取实际的带泛型的事件类型
// 比如PayloadApplicationEvent<MyEvent>
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 {
// 1.获取一个applicationEventMulticaster
// 2.广播事件
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
} // 如果当前容易存在父容器,父容器也要发布事件
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
- 我们来看下ApplicationEventMulticaster的初始化
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 我们自定义了ApplicationEventMulticaster,就直接用我们自定义的
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
// 如果我们没定义ApplicationEventMulticaster,默认使用SimpleApplicationEventMulticaster
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
}
通过这段代码,我们可以知道,我们如果申明了一个ApplicationEventMulticaster类,会覆盖默认的SimpleApplicationEventMulticaster。
- 注册监听者
protected void registerListeners() {
// 把提前存储好的监听器添加到监听器容器中
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
} // Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let post-processors apply to them!
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
} // Publish early application events now that we finally have a multicaster...
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (earlyEventsToProcess != null) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
- 看完上面的代码,我相信大家多spring的publish-event机制已经有了一定的了解,现在我们再来探讨一个问题-------怎么实现异步机制?
我继续看
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType)
这段代码:@Override
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);
}
}
}
可以发现,在通知监听者的时候,会去检查ApplicationEventMulticaster对象中是否配置了Executor执行器,如果配置了的话,会将invokeListener(listener, event)封装成一个线程可执行任务,然后给执行器执行。
这样的话,我们可以自定义一个ApplicationEventMulticaster:
//需要注意的是,我们的名字一定要叫applicationEventMulticaster,否则不生效,回头看下源码就明白了
@Component("applicationEventMulticaster")
public class MyMulticaster extends SimpleApplicationEventMulticaster { @Override
protected Executor getTaskExecutor() {
// 这里可以按实际需求进行线程池的配置
return new SimpleAsyncTaskExecutor();
} /**
* 这个方法主要配置错误处理,默认只会打一个警告级别的日志
*
* @return
*/
@Override
protected ErrorHandler getErrorHandler() {
return (t -> {
System.out.println("出错了");
});
}
}
通过上面的配置后,我们所有的publish-event都会通过异步的机制去执行,但是我们想一个问题,有时候我们不想异步呢?一部分我们想同步,一部分想异步,这个时候怎么做呢?我们可以通过下面这种方式:
配置类上加注解
@EnableAsync
// 执行的方法上加@Async注解
@EventListener
@Async
public void on(MySecondEvent event) throws Exception { }
这样通过aop的方式,会将我们的方法采用异步的方式进行执行。我们没有配置线程池的时候可能会报错:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.core.task.TaskExecutor' available
不过我们方法还是会异步执行,这主要是下面这段代码:
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
if (beanFactory != null) {
try {
// 在这里会抛出一个NoSuchBeanDefinitionException
return beanFactory.getBean(TaskExecutor.class);
}
catch (NoUniqueBeanDefinitionException ex) {
logger.debug("Could not find unique TaskExecutor bean", ex);
try {
return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isInfoEnabled()) {
logger.info("More than one TaskExecutor bean found within the context, and none is named " +
"'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly " +
"as an alias) in order to use it for async processing: " + ex.getBeanNamesFound());
}
}
}
// 这里被捕获
catch (NoSuchBeanDefinitionException ex) {
logger.debug("Could not find default TaskExecutor bean", ex);
try {
// 我们也没有定义一个名字是DEFAULT_TASK_EXECUTOR_BEAN_NAME = "taskExecutor"
// 的线程池
return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);
}
catch (NoSuchBeanDefinitionException ex2) {
logger.info("No task executor bean found for async processing: " +
"no bean of type TaskExecutor and no bean named 'taskExecutor' either");
}
// Giving up -> either using local default executor or none at all...
}
}
// 最后会返回一个null
return null;
}
@Override
@Nullable
// 最后会返回一个SimpleAsyncTaskExecutor
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}
Spring 学习 之 再探publish-event机制的更多相关文章
- Spring学习之旅(四)Spring工作原理再探
上篇博文对Spring的工作原理做了个大概的介绍,想看的同学请出门左转.今天详细说几点. (一)Spring IoC容器及其实例化与使用 Spring IoC容器负责Bean的实例化.配置和组装工作有 ...
- 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)
再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...
- spring学习12 -Spring 框架模块以及面试常见问题注解等
以下为spring常见面试问题: 1.Spring 框架中都用到了哪些设计模式? Spring框架中使用到了大量的设计模式,下面列举了比较有代表性的: 代理模式—在AOP和remoting中被用的比较 ...
- Spring 学习记录8 初识XmlWebApplicationContext(2)
主题 接上文Spring 学习记录7 初识XmlWebApplicationContext refresh方法 refresh方法是定义在父类AbstractApplicationContext中的. ...
- 【再探backbone 02】集合-Collection
前言 昨天我们一起学习了backbone的model,我个人对backbone的熟悉程度提高了,但是也发现一个严重的问题!!! 我平时压根没有用到model这块的东西,事实上我只用到了view,所以昨 ...
- 不错的Spring学习笔记(转)
Spring学习笔记(1)----简单的实例 --------------------------------- 首先需要准备Spring包,可从官方网站上下载. 下载解压后,必须的两个包是s ...
- spring学习 8-面试(事务,解决线程安全)
1.介绍一下Spring的事物管理 参考:Spring 学习7 -事务 2.Spring如何处理线程并发问题 Spring使用ThreadLocal解决线程安全问题 参考:Spring学习11- ...
- Spring学习(一)初识Spring
什么是Spring 定义:Spring 是一个轻量级的 DI / IoC 和 AOP 容器的开源框架,目的为了简化java开发. DI:注入 IOC:控制反转 AOP:面向切面编程 原理:利用了jav ...
- Spring学习笔记之二----基于XML的Spring AOP配置
在Spring配置文件中,通常使用<aop:config>元素来设置AOP,其中应包括: <aop:aspect>指定aspect,aspect是一个POJO类,包含了很多的a ...
随机推荐
- Arthas-Java的线上问题定位工具
Arthas(阿尔萨斯) 能为你做什么? Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱. 当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决: 这个类从哪个 jar ...
- mybatis配置的逻辑删除不好使了
在使用mybatisplus中,可使用逻辑删除.案例中,使用mybatisplus逆向生成model,使用delete_status为识别逻辑删除字段. springboot 中配置启动逻辑删除 my ...
- Mybatis源码详解系列(三)--从Mapper接口开始看Mybatis的执行逻辑
简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...
- SpringMVC转发及重定向
基础环境搭建请参考SringMVC入门程序 1:springmvc-servlet.xml <?xml version="1.0" encoding="UTF-8& ...
- 【原创】Linux RCU原理剖析(二)-渐入佳境
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- Django文档阅读-Day3
Django文档阅读-Day3 Writing your first Django app, part 3 Overview A view is a "type" of Web p ...
- 你知道什么是 GitHub Action 么?
本文是 GitHub Action 的入门教程,如您已有相关使用经验可以直接关掉. GitHub Action 是 GitHub 于 2018 年 10 月推出的一个 CI\CD 服务. 之前一直都是 ...
- java中String StringBuilder StringBuffer比较和效率(性能)测试
string stringbuilder stringbuffer三者的区别 从JDK源码看,String.StringBuilder.StringBuffer都是存放在char[] 数组字符串. 简 ...
- Java锁之自旋锁
Java锁之自旋锁 自旋锁:spinlock,是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU 原来提到的比较并交换,底层 ...
- shell脚本:备份数据库、代码上线
备份MySQL数据库场景:一台MySQL服务器,跑着5个数据库,在没有做主从的情况下,需要对这5个库进行备份 需求:1)每天备份一次,需要备份所有的库2)把备份数据存放到/data/backup/下3 ...