SpringFramework — 事件机制&监听器

小龙 568 2022-05-12

前言

说到事件和监听器,最先想到的估计就是观察者模式了吧。下面来复习一下观察者设计模式

观察者设计模式

观察者设计模式,也被称为发布订阅模式,是行为型设计模式中的一种,观察者模式关注的点是某一个对象被修改 / 做出某些反应 / 发布一个信息等,会自动通知依赖它的对象(订阅者)。

image-1652321688336

观察者模式的三大核心是:观察者被观察主题订阅者。观察者( Observer )需要绑定要通知的订阅者( Subscriber ),并且要观察指定的主题( Subject )。

代码Demo

/**
 * 抽象观察者
 */
public interface ObServer {
    void update();
}
/**
* 具体观察者
*/

public class EndConCreteObServer implements ObServer{
    @Override
    public void update() {
        System.out.println("End 发现老板,暂停摸鱼,回归工作状态,以免被开除");
    }
}

public class EndConCreteObServer implements ObServer{
    @Override
    public void update() {
        System.out.println("End 发现老板,暂停摸鱼,回归工作状态,以免被开除");
    }
}
/**
 * 主题(订阅者)
 */
public class Subject {
    // 保存所有监听者的集合
    private static final ArrayList<ObServer> OB_SERVERS = new ArrayList<>();

    /**
     * 新增监听者
     * @param obServer
     */
    public void subscribe(ObServer obServer){
        OB_SERVERS.add(obServer);
    }

    /**
     * 删除监听者
     * @param obServer
     */
    public void unSubscribe(ObServer obServer){
        OB_SERVERS.remove(obServer);
    }

    /**
     * 通知所有监听者
     */
    public void notifyAllObServer(){
        OB_SERVERS.stream().forEach( o -> {
            o.update();
        });
    }
}
/**
 * 具体主题(被观察者)
 */
public class BossConCreteSubject extends Subject {
    public void doSomething(){
        System.out.println("老板完成了自己的工作");
        System.out.println("老板开始巡查工作");
        super.notifyAllObServer();
    }
}
/**
* 测试类
*/
public class Demo {
    public static void main(String[] args) {
        // 实例化具体主题
        BossConCreteSubject subject = new BossConCreteSubject();
        // 实例化具体观察者
        ObServer rs = new RstheConCreteObServer();
        // 向主题中添加观察者
        subject.subscribe(rs);
        // 实例化具体观察者
        ObServer end = new EndConCreteObServer();
                // 向主题中添加观察者
        subject.subscribe(end);
        // 执行主题
        subject.doSomething();
    }
}

执行结果

老板完成了自己的工作
老板开始巡查工作
Rsthe 发现老板,暂停摸鱼,回归工作状态,以免被开除
End 发现老板,暂停摸鱼,回归工作状态,以免被开除

Process finished with exit code 0

允许你定义一种订阅机制(主题), 可在对象事件发生时通知多个 “观察” 该对象的其他对象。

SpringFramework中设计的观察者模式

SpringFramework 中,体现观察者模式的特性就是事件驱动和监听器。监听器充当订阅者,监听特定的事件;事件源充当被观察的主题,用来发布事件;IOC 容器本身也是事件广播器,可以理解成观察者

可以理解把 SpringFramework 的事件驱动核心概念划分为 4 个:事件源事件广播器监听器

  • 事件源:发布事件的对象

  • 事件:事件源发送的信息 / 作出的动作

  • 广播器:事件真正广播给监听器的对象,即:ApplicationContext

    • ApplicationContext 接口实现了 ApplicationEventPublisher 接口,具备事件广播的发布事件的能力
    • ApplicationEventMulticaster 组合了所有的监听器。具备事件广播器的广播事件的能力
  • 监听器:监听事件的对象

这样理解起来会更容易一些:

image-1652322579387

代码理解SpringFramework中的事件与监听器

编写监听器

SpringFramework 中内置的监听器接口是 ApplicationListener ,它还带了一个泛型,代表要监听的具体事件:

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
	void onApplicationEvent(E event);
}

我们要自定义监听器,只需要实现这个 ApplicationListener 接口即可。

为快速体会事件和监听器的功能,下面咱先介绍两个事件:ContextRefreshedEventContextClosedEvent ,它们分别代表容器刷新完毕即将关闭。下面咱编写一个监听器,来监听 ContextRefreshedEvent 事件。

@Component
public class ContextRefreshedApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		System.out.println("ContextRefreshedApplicationListener 监听到 ContextRefreshedEvent(容器刷新)事件");
	}
}

编写启动类

public class QuickstartListenerApplication {
	public static void main(String[] args) {
		System.out.println("准备初始化IOC容器");
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.rsthe.eventAndListenerDemo");
		System.out.println("IOC容器初始化完成");
		ctx.close();
		System.out.println("IOC关闭");
	}
}

执行结果

准备初始化IOC容器
ContextRefreshedApplicationListener 监听到 ContextRefreshedEvent(容器刷新)事件
IOC容器初始化完成
IOC关闭

注解式监听器

除了实现 ApplicationListener 接口之外,还可以使用注解的形式注册监听器。

使用注解式监听器,组件不再需要实现任何接口,而是直接在需要作出事件反应的方法上标注 @EventListener 注解即可:

@Component
public class ContextCloseApplicationListener {
	@EventListener
	public void onApplicationEvent(ContextClosedEvent event) {
		System.out.println("ContextRefreshedApplicationListener 监听到 ContextRefreshedEvent(容器刷新)事件");
	}
}

执行结果

准备初始化IOC容器
ContextRefreshedApplicationListener 监听到 ContextRefreshedEvent(容器刷新)事件
IOC容器初始化完成
ContextCloseApplicationListener 监听到 ContextClosedEvent(容器关闭)事件
IOC关闭

从上面两段监听器事件Demo,可以得出几个结论:

  • ApplicationListener 会在容器初始化阶段就准备好,在容器销毁时一起销毁;

  • ApplicationListener 也是 IOC 容器中的普通 Bean ;

  • IOC 容器中有内置的一些事件供我们监听。

SpringFramework中的内置事件【熟悉】

在 SpringFramework 中,已经有事件的默认抽象,以及 4 个默认的内置事件了

ApplicationEvent

它是事件模型的抽象,它是一个抽象类,里面也没有定义什么东西,只有事件发生时的时间戳。,它是继承自 jdk 原生的观察者模式的事件模型,并且把它声明为抽象类:

public abstract class ApplicationEvent extends EventObject

文档说明

Class to be extended by all application events. Abstract as it
doesn't make sense for generic events to be published directly.

由所有应用程序事件扩展的类。它被设计为抽象的,因为直接发布一般事件没有意义。

ApplicationContextEvent

public abstract class ApplicationContextEvent extends ApplicationEvent {
    
	public ApplicationContextEvent(ApplicationContext source) {
		super(source);
	}
    
	public final ApplicationContext getApplicationContext() {
		return (ApplicationContext) getSource();
	}
}

它在构造时,会把 IOC 容器一起传进去,这意味着事件发生时,可以通过监听器直接取到 ApplicationContext 而不需要做额外的操作,这才是 SpringFramework 中事件模型扩展最值得的地方。下面列举的几个内置的事件,都是基于这个 ApplicationContextEvent 扩展的。

ContextRefreshedEvent&ContextClosedEvent

这两个是一对,分别对应着 IOC 容器刷新完毕但尚未启动,以及 IOC 容器已经关闭但尚未销毁所有 Bean 。

ContextStartedEvent&ContextStoppedEvent

这一对跟上面的时机不太一样了。ContextRefreshedEvent 事件的触发是所有单实例 Bean 刚创建完成后,就发布的事件,此时那些实现了 Lifecycle 接口的 Bean 还没有被回调 start 方法。当这些 start 方法被调用后,ContextStartedEvent 才会被触发。同样的,ContextStoppedEvent 事件也是在 ContextClosedEvent 触发之后才会触发,此时单实例 Bean 还没有被销毁,要先把它们都停掉才可以释放资源,销毁 Bean

自定义事件开发

SpringFramework中内置许多的事件,但是我们想要自己在合适的时机发布一些事件,让指定的监听器来以此作出反应,执行特定的逻辑,那就需要自定义事件了。

模拟一个场景来开发自定义事件

在一些网站中当新用户注册成功后,会同时发送短信、邮件、站内信,通知用户注册成功,并且发放积分。

在这个场景中,用户注册成功后,广播一个“用户注册成功”的事件,将用户信息带入事件广播出去,发送短信、邮件、站内信的监听器监听到注册成功的事件后,会分别执行不同形式的通知动作。

自定义用户注册成功事件

/**
 * 自定义用户成功注册事件
 */
public class RegisterSuccessEvent extends ApplicationEvent {
	public RegisterSuccessEvent(Object source) {
		super(source);
	}
}

编写监听器

发邮件

@Component
public class EmailSenderListener implements ApplicationListener<RegisterSuccessEvent> {
	@Override
	public void onApplicationEvent(RegisterSuccessEvent event) {
		System.out.println("[EmailSenderListener]监听到新用户注册成功,发送邮件。。。。");
	}
}

发站内短信

@Component
public class MessageSenderListener {
	@EventListener
	public void onApplicationEvent(RegisterSuccessEvent event) {
		System.out.println("[MessageSenderListener]监听到新用户注册成功,发送站内短信。。。。");
	}
}

发短信

@Component
public class SmsSenderListener implements ApplicationListener<RegisterSuccessEvent> {
	@Override
	public void onApplicationEvent(RegisterSuccessEvent event) {
		System.out.println("[SmsSenderListener]监听到新用户注册成功,发送短信。。。。");
	}
}

编写注册逻辑业务层

只有事件和监听器还不够,还需要有一个事件源来持有事件发布器,在应用上下文中发布事件。

Service 层中,需要注入 ApplicationEventPublisher 来发布事件,此处选择使用回调注入的方式。

@Service
public class RegisterServiceImpl implements ApplicationEventPublisherAware {
	private ApplicationEventPublisher publisher;
	public void register(String name) {
		System.out.println(name + " 注册成功。。。");
		publisher.publishEvent(new RegisterSuccessEvent(name));
	}

	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
		this.publisher = publisher;
	}
}

编写测试启动类

public class CustomEventApplication {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.rsthe.customEvent");
		RegisterServiceImpl registerService = ctx.getBean(RegisterServiceImpl.class);
		registerService.register("rsthe");
	}
}

执行结果

rsthe 注册成功。。。
[EmailSenderListener]监听到新用户注册成功,发送邮件。。。。
[SmsSenderListener]监听到新用户注册成功,发送短信。。。。
[MessageSenderListener]监听到新用户注册成功,发送站内短信。。。。

调整监听器的触发顺序

如果业务需要调整,需要先发送站内信,后发送邮件,这个时候就需要配合另外一个注解了:@Order。标注上这个注解后,默认的排序值为 Integer.MAX_VALUE ,代表最靠后。

按照这个规则,那咱在 MessageSenderListener 的 onRegisterSuccess 方法上标注 @Order(0) ,重新运行启动类的 main 方法,观察控制台的打印:

@Component
public class MessageSenderListener {
	@Order(0)
	@EventListener
	public void onApplicationEvent(RegisterSuccessEvent event) {
		System.out.println("[MessageSenderListener]监听到新用户注册成功,发送站内短信。。。。");
	}
}

执行结果

rsthe 注册成功。。。
[MessageSenderListener]监听到新用户注册成功,发送站内短信。。。。
[EmailSenderListener]监听到新用户注册成功,发送邮件。。。。
[SmsSenderListener]监听到新用户注册成功,发送短信。。。。

事件广播原理

SpringFramework中有两个事件核心组件:ApplicationEventPublisherApplicationEventMulticaster,它们分别代表 事件发布器事件广播器

这两个组件不是一回事,事件发布器是用来接收事件的,并交给事件广播器处理;事件广播器拿到事件发布器发布的事件,并广播给监听器。在观察者模式中,观察者就是这两者的合体,只不过在 SpringFramework 中把职责拆分开了而已。

ApplicationContext 接口继承了 ApplicationEventPublisher ,拥有事件发布的功能;ApplicationContext 的第一个抽象实现类 AbstractApplicationContext 组合了一个 ApplicationEventMulticaster ,拥有事件广播的能力。综合来看,ApplicationContext 的落地实现就已经能够完成事件驱动模型中的 “观察者” 身份了。

发布事件

发布事件是 ApplicationEventPublisher 接口的 publishEvent 方法。

	public void publishEvent(ApplicationEvent event) {
		publishEvent(event, null);
	}
    
    public void publishEvent(Object 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; // 这里要给普通的对象封装为PayloadApplicationEvent
		if (event instanceof ApplicationEvent) {// 如果事件是普通 ApplicationEvent 事件
			applicationEvent = (ApplicationEvent) event;
		}
		else { // 不是普通 ApplicationEvent 事件,封装成 PayloadApplicationEvent 事件
			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);
		}
		// 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);
			}
		}
	}

分开来看,广播事件可以分为三个部分

  1. 适配 payload 类型的 ApplicationEvent

  2. 在本容器中广播事件

  3. 通知父容器发布的事件(这里解释了为什么子容器事件会广播到父容器)

ApplicationEventMulticaster广播事件

ApplicationEventMuticaster 接口唯一的落地实现类:SimpleApplicationEventMulticaster

	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}

	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

包装事件类型

方法的第一行代码,它执行了一个 resolveDefaultEventType 方法,它用来包装 ApplicationEvent 的具体类型,目的是可以更方便的获取对象和类的一些信息(父类、接口、泛型等)。这个方法的返回值类型是 ResolvableType ,这个东西我们还是拿来说一下的。

ResolvableTypeSpringFramework4.0 中提供的,其目的使用俩处理泛型类型的便捷API。使用 ResolvableType 可以更方便的解析、获取到指定类、属性、方法中的泛型类型。注意,它只能简化操作,并不能在 Java 原生的反射上做更多的事。

ResolvableType 的使用

public static void main(String[] args) throws Exception {
    ResolvableType resolvableType = ResolvableType.forClass(类.class);
    System.out.println(resolvableType.getInterfaces()[0].resolveGeneric(0));
}

获取异步执行的线程池

Executor executor = getTaskExecutor();

获取 TastExecutor 任务执行器,这个 Executor 是 JUC 中的,它的具体实现想必小伙伴们可能不陌生:ExecuteService 线程池。而在这里具体要获取到的 TaskExecutor 是 SpringFramework 基于 Executor 扩展的,而且作了函数式接口标记,方法还是 Executor 的方法,没有变过。

@FunctionalInterface
public interface TaskExecutor extends Executor {
	@Override
	void execute(Runnable task);
}

获取匹配的监听器,广播事件

下一步就是要取监听器了,注意传入的参数有两个哦:getApplicationListeners(event, type) ,包含事件本身,和事件的类型。

		// 遍历匹配的监听器
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			// 如果线程池不为空
			if (executor != null) {
				// 把事件广播到匹配的监听器中的任务提交到线程池中
				executor.execute(() -> invokeListener(listener, event));
			} else {
				invokeListener(listener, event);
			}
		}

拿到监听器后,它执行 invokeListener 方法,很明显它要给监听器广播事件了。

	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);
		}
	}

最核心的方法:doInvokeListener,以 do 开头的方法就是真正执行功能的方法。

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			// 完成事件广播
			listener.onApplicationEvent(event);
		} catch (ClassCastException ex) {
		//	... ... ... ...
	}

获取监听器的逻辑

for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {}
public abstract class AbstractApplicationEventMulticaster
		implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
	final Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
    protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
    	// 获取事件最初发生的对象
		Object source = event.getSource();
		Class<?> sourceType = (source != null ? source.getClass() : null);
		// 监听器缓存Key 对象
		ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
		// Potential new retriever to populate
		CachedListenerRetriever newRetriever = null;
		// Quick check for existing entry on ConcurrentHashMap
		// 此处使用监听器缓存,保证快速取出监听器
		CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);
		if (existingRetriever == null) {
			// Caching a new ListenerRetriever if possible
			if (this.beanClassLoader == null ||
					(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
							(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
				newRetriever = new CachedListenerRetriever();
				existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
				if (existingRetriever != null) {
					newRetriever = null;  // no need to populate it in retrieveApplicationListeners
				}
			}
		}
		if (existingRetriever != null) {
			Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
			if (result != null) {
				return result;
			}
			// If result is null, the existing retriever is not fully populated yet by another thread.
			// Proceed like caching wasn't possible for this current local attempt.
		}
		return retrieveApplicationListeners(eventType, sourceType, newRetriever);
    }
}

retrieverCache 中保存了指定事件对应的监听器,而缓存的来源就是真正获取监听器的 retrieveApplicationListeners 方法。

private Collection<ApplicationListener<?>> retrieveApplicationListeners(
        ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {

    List<ApplicationListener<?>> allListeners = new ArrayList<>();
    Set<ApplicationListener<?>> listeners;
    Set<String> listenerBeans;
    synchronized (this.retrievalMutex) {
        // defaultRetriever中存放了所有的监听器
        listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
        listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
    }

    // Add programmatically registered listeners, including ones coming
    // from ApplicationListenerDetector (singleton beans and inner beans).
    // 逐个检查监听器是否支持当前事件,此处的监听器来源是编程式添加
    // ( addApplicationListener )
    for (ApplicationListener<?> listener : listeners) {
        if (supportsEvent(listener, eventType, sourceType)) {
            if (retriever != null) {
                retriever.applicationListeners.add(listener);
            }
            allListeners.add(listener);
        }
    }

    // Add listeners by bean name, potentially overlapping with programmatically
    // registered listeners above - but here potentially with additional metadata.
    // 同样是检查监听器是否支持当前事件,不过此处的监听器来源是声明式/配置式
    // ( @Component、<bean>等 )
    if (!listenerBeans.isEmpty()) {
        ConfigurableBeanFactory beanFactory = getBeanFactory();
        for (String listenerBeanName : listenerBeans) {
            try {
                if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
                    ApplicationListener<?> listener =
                            beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                    if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
                        if (retriever != null) {
                            if (beanFactory.isSingleton(listenerBeanName)) {
                                retriever.applicationListeners.add(listener);
                            }
                            else {
                                retriever.applicationListenerBeans.add(listenerBeanName);
                            }
                        }
                        allListeners.add(listener);
                    }
                } // else 移除监听
            } // catch ......
        }
    }

    // 监听器排序
    AnnotationAwareOrderComparator.sort(allListeners);
    if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
        retriever.applicationListeners.clear();
        retriever.applicationListeners.addAll(allListeners);
    }
    return allListeners;
}

体概括一下这个过程,它可以分为三个步骤:

  1. 筛选出由编程式注入到 IOC 容器的,监听当前发布事件的监听器
  2. 筛选出由声明式 / 配置式注入到 IOC 容器的,监听当前发布事件的监听器
  3. 监听器排序

小结问题

  1. SpringFramework 对于层次性事件的处理策略是什么?是如何实现的?

  2. payload 事件与普通事件有什么区别?SpringFramework 在底层是如何兼容它的?

  3. 册,通过 Debug ,完整的跟一边源码的流程,体会 SpringFramework 中的事件驱动控制。

  1. 容器发布的事件会向上传播到父容器,父容器发布的事件不会向下传播。

  2. pplicationEventPublisher(事件发布器) 和 ApplicationEventMulticaster(事件广播器),事件发布器是用来接受事件,并交给事件广播器处理;事件广播器拿到事件发布器的事件,并广播给监听器。在观察者模式中,【观察者就是这两者的合体】。

3.AbstractApplicationContext#publishEvent(Object, ResolvableType)实现:1.适配 payload 类型的 ApplicationEvent 2.在本容器中广播事件 3.通知父容器发布事件(正好知道了为什么子容器的事件会广播到父容器)

4.广播事件步骤:包装事件的类型(为了拿到泛型)、获取匹配的监听器,(异步)执行invokeListener (doInvokeListener)

5.ResolvableType 作用:主要是能方便的取出类、对象、继承的抽象类、实现的接口等等,上面标注的【泛型类型】

6.获取监听器的逻辑:缓存的双检锁设计,AbstractApplicationEventMulticaster#getApplicationListeners(ApplicationEvent, ResolvableType) --> AbstractApplicationEventMulticaster#retrieveApplicationListeners


# IOC