[Spring Boot 源解系列] 监听者模式在 Spring Boot

2,847 阅读10分钟

本文主题

这篇文章,我想记录下我刚学知识 - 监听器模式,并且想讲监听器模式在 Spring Boot 中的实现。本文会分为几个 section 来写:
  1. 讲讲监听模式及其应用场景,并写一个小栗子
  2. 记录一下 监听器模式 In Spring Boot 的应用
  3. 写一下关于监听器模式的 Spring Boot 源解

Lets go!

监听器模式

在设计模式中,大家可能都听说一种 23 种设计模式中的观察者模式,同时也叫做发布-订阅模式。观察者模式的用处在于,当一个目标(被观察者)管理所有相依于它的观察者,并且在它本身的状态改变时主动发出通知。这通常通过调用各观察者所提供的方法来实现。所以我们抽象出来目标对象与观察者。这样有助于模块的划分,让模块专注于某个功能的实现,提高了系统的可维护性和重用性。
而监听器是观察者模式的实现方案之一,也是发布-订阅模。在面向对象中,它拆解成了几个类型,分别是以下:

在上图中,监听器的实现被设计抽象为三个部分:广播者[Multicast],事件[Event]以及监听器[Listener]。其中监听器和事件进行了解耦,监听器只需要定义好自己感兴趣的事件便可监听;而广播者负责将事件在合适的时间点将事件广播出去。

下面我们实践一个简单的小栗子来讲解。

栗子

记得纪录片<中国大厨>里面有一个令我影响深刻的片段。就是当食材准备后,主厨会在不同时段负责高效率调配厨房里每一个负责不同部分的人去干活,犹如一个在战场上运筹帷幄的将军。例如说,主厨会下令让一些人去洗菜,让一些人去炒菜,让一些人腌肉等。在整个过程中,他们负责的工作是不同的,他们执行的时间点也是不同的。

事件[Event]

所以我们从中抽取几个事件[Event]
1. WashVegetablesEvent 洗菜事件
2. FireDishEvent 炒菜事件
3. ServeDishEvent 上菜事件

我们先定义一个抽象类,它叫 KitchenEvent,意指“厨房事件”

public abstract class KitchenEvent {
    // 获取厨房状态
    public abstract String getState();
}

写“洗菜”事件

public class WashVegetablesEvent extends KitchenEvent {
    @Override
    public String getState() {
        return "洗菜事件";
    }
}

写“炒菜事件”

public class FireDishEvent extends KitchenEvent {
    @Override
    public String getState() {
        return "炒菜事件";
    }
}

写“上菜事件”

public class ServeDishEvent extends KitchenEvent {
    @Override
    public String getState() {
        return "上菜事件";
    }
}

监听器[Listener]

命令写好了,那么监听命令(执行命令)的人是谁呢?当然是服务员,厨师,帮厨。那么可以把这几个角色作为监听器,监听着来自主厨的命令。他们的共同点都是在等待命令,所以我们可以将之进行抽象

KitChenListener.java

public interface KitChenListener {
    void onKitChenEvent(KitchenEvent kitchenEvent);
}

首先是服务员

public class WaiterListener implements KitChenListener {
    @Override
    public void onKitChenEvent(KitchenEvent kitchenEvent) {
        if (kitchenEvent instanceof ServeDishEvent) {
            System.out.println(kitchenEvent.getState());
        }
    }
}

然后是厨师

public class CookListener implements KitChenListener {
    @Override
    public void onWeatherEvent(KitchenEvent kitchenEvent) {
        if (kitchenEvent instanceof FireDishEvent) {
            System.out.println(kitchenEvent.getState());
        }
    }
}

然后是帮厨

public class HelpKitChenListener implements KitChenListener {
    @Override
    public void onWeatherEvent(KitchenEvent kitchenEvent) {
        if (kitchenEvent instanceof WashVegetablesEvent) {
            System.out.println(kitchenEvent.getState());
        }
    }
}

广播器[Multicater]

ok,上面事件[Event]准备好了,监听器[Listener]也准备好了,那么我们来写发号命令的人,那就是主厨。首先,我们先实现一个 Multicater 接口,定义好广播功能
public interface EventMulticaster {
    void multicastEvet(KitchenEvent kitchenEvent);

    void addListener(KitchenEvent kitchenEvent);

    void removeListener(KitchenEvent kitchenEvent);
}

然后我们使用抽象类

public abstract class AbstractEventMulticaster implements EventMulticaster {


    private List<KitchenEvent> kitchenEventList = new ArrayList<>();
    @Override
    public void multicastEvet(KitchenEvent kitchenEvent) {
        start();
        kitchenEventList.forEach(i -> i.getState(kitchenEvent));
        doEnd();
    }

    //定义两个抽象回调,由子类实现
    protected abstract  void doEnd();
    protected abstract void start();

    @Override
    public void addListener(KitchenEvent kitchenEvent) {
        kitchenEventList.add(kitchenEvent);
    }

    @Override
    public void removeListener(KitchenEvent kitchenEvent) {
        kitchenEventList.remove(kitchenEvent);
    }
}

我们实现广播器

public class KitchenEventMulticaster extends AbstractEventMulticaster{
    @Override
    protected void doEnd() {
        System.out.println("end");
    }

    @Override
    protected void start() {
        System.out.println("start");
    }
}

测试demo[TestDemo]

public class Test {
    public static void main(String[] args) {
        // 实例化广播器
        KitchenEventMulticaster kitchenEventMulticaster = new KitchenEventMulticaster();
        // 实例化监听器
        WaiterListener waiterListener = new WaiterListener();
        CookListener cookListener = new CookListener();
        HelpKitChenListener helpKitChenListener = new HelpKitChenListener();
        // 添加监听器
        kitchenEventMulticaster.addListener(waiterListener);
        kitchenEventMulticaster.addListener(cookListener);
        kitchenEventMulticaster.addListener(helpKitChenListener);
        // 添加事件
        WashVegetablesEvent washVegetablesEvent = new WashVegetablesEvent();
        FireDishEvent fireDishEvent = new FireDishEvent();
        ServeDishEvent serveDishEvent = new ServeDishEvent();
        // 广播事件
        kitchenEventMulticaster.multicastEvet(washVegetablesEvent);
        kitchenEventMulticaster.multicastEvet(fireDishEvent);
        kitchenEventMulticaster.multicastEvet(serveDishEvent);
    }
}

监听模式 In Spring Boot

下面我来讲解一下在 Spring Boot 中如何实现监听器。实现的方式有三种
1. 通过实现 ApplicationListener 接口
2. 通过 application.properties 添加
3. 通过 SpringApplication 进行添加监听器

通过实现 ApplicationListener 接口

直接创建一个类,然后实现对应的监听器接口。

@Component
public class MyApplicationListener implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println(event.getClass());
    }
}

注意的是,我们可以在 ApplicationListener 的尖括号中加入自己感兴趣的事件(下面源解的时候会列举有哪些事件)。例如

@Component
public class MyApplicationListener implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        System.out.println(event.getClass());
    }
}

通过 application.properties 添加

application.properties

context.listener.classes=com.listener.MyApplicationListener

ps:这个 MyApplicationListener 是必须要实现 ApplicationListener 接口的。

通过 SpringApplication 进行添加监听器

```java @SpringBootApplication public class BlogApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(BlogApplication.class, args); //把监听器加入到SpringApplication中 context.addApplicationListener(new MyApplicationListener()); //发布事件 context.publishEvent(new MyApplicationEvent(new Object())); context.close(); } } ```

源码解析

版本说明:Spring Boot 2.2.4.RELEASE

Debug 工具:Idea

首先我看一下大体上的流程

看完上面的简图,相信各位心里对整个流程有一定的了解了。然后我介绍一下 Spring Boot 会广播哪几类事件。

ok。我们一步一步从源码开始入手。

第一个,我们的启动类是从 SpringApplication.java 开始的。下图是 SpringApplication.run() 方法片段代码的截图

我们可以看这样一个小段代码。这段代码代表的是,Spring 广播了 ApplicationStartingEvent(应用启动 Event),这里对应删改你的步骤是第二步。

	listeners.starting();

然后我们围绕这段代码开始展开源码解析。debug 深入后,我们发现这是一个 SpringApplicationRunListener 类。介绍一下,SpringApplicationRunListener 实际上是一个监听类,同时也像个代理类。在 Spring Boot 的整个流程当中,它是接收不同执行点 Event 通知的监听者。像如果 Spring Boot 像调用启动事件,直接 starting 或 started 等方法就可以进行广播了,提高了封装,降低了外部的感知。

SpringApplicationRunListener 的 starting 方法中,调用了 SimpleApplicationEventMulticasterSimpleApplicationEventMulticaster 实现了 AbstractApplicationEventMulticaster(提供对监听器基本的操作)。而 SimpleApplicationEventMulticaster 是负责广播 Event 的。值得一提的是,我们可以通过 setTaskExecutor() 自定义 SimpleApplicationEventMulticastertaskExecutor 的属性,这是一个扩展点。

进入了 SimpleApplicationEventMulticaster 后我们会看到它发布 Event 的方法

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

我们看的的 resolveDefaultEventType 方法是负责校验 event 的类型。确定类型后方便后续监听器进行判断是否接受此 event。确定类型后我们进入了 multicastEvent 的同名方法

	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	   ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	   Executor executor = getTaskExecutor();
		// getApplicationListeners 方法对应的是第六步
		// for 循环体代码对应的是 第十步
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) { 
			if (executor != null) {
			   executor.execute(() -> invokeListener(listener, event));
			}
			else {
			   invokeListener(listener, event);
			}
		}
	}

细看上面代码的注释,对应着步骤图里面的哪一个步骤。然后我们进去 getApplicationListeners() 看看。

	protected Collection<ApplicationListener<?>> getApplicationListeners(
			ApplicationEvent event, ResolvableType eventType) {
		// eventType 代表的是事件的类型(如果是 startevent,这是 ApplicationStartingEvent)
		// sourceType  代表的是调用的来源(这里是 SpringApplication)
		Object source = event.getSource();
		Class<?> sourceType = (source != null ? source.getClass() : null);
		// 封装成一个 cachekey,快速缓存方便查找
		ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

		// 查看缓存,如果有立即返回
		ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
		if (retriever != null) {
			return retriever.getApplicationListeners();
		}

		if (this.beanClassLoader == null ||
				(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
						(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
			// 同步苏定,防止其他线程同时调用
			synchronized (this.retrievalMutex) {
				retriever = this.retrieverCache.get(cacheKey);
				// 再次查看缓存
				if (retriever != null) {
					return retriever.getApplicationListeners();
				}
				retriever = new ListenerRetriever(true);
				// 如果缓存没有的话,就调用 retrieveApplicationListeners
				Collection<ApplicationListener<?>> listeners =
						retrieveApplicationListeners(eventType, sourceType, retriever);
				// 添加进缓存
				this.retrieverCache.put(cacheKey, retriever);
				return listeners;
			}
		}
		else {
			// No ListenerRetriever caching -> no synchronization necessary
			return retrieveApplicationListeners(eventType, sourceType, null);
		}
	}

我们重点看 retrieveApplicationListeners 方法。这方法说白就是根据跟定条件找到对应 event 的监听器。

	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.applicationListeners 里面就是步骤图里第一步加载过来的所有监听器
			listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
			// defaultRetriever.applicationListenerBeans 一般都是为 null 的
			listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
		}

		// Add programmatically registered listeners, including ones coming
		// from ApplicationListenerDetector (singleton beans and inner beans).
		// 循环监听器
		for (ApplicationListener<?> listener : listeners) {
		    // supportsEvent 方法对应的步骤图里得第七步
		    // 返回是,即可加入集合;反之则不加入
			if (supportsEvent(listener, eventType, sourceType)) {
				if (retriever != null) {
					retriever.applicationListeners.add(listener);
				}
				allListeners.add(listener);
			}
		}

		// 这里是通过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 {
						// Remove non-matching listeners that originally came from
						// ApplicationListenerDetector, possibly ruled out by additional
						// BeanDefinition metadata (e.g. factory method generics) above.
						Object listener = beanFactory.getSingleton(listenerBeanName);
						if (retriever != null) {
							retriever.applicationListeners.remove(listener);
						}
						allListeners.remove(listener);
					}
				}
				catch (NoSuchBeanDefinitionException ex) {
                    // ...
				}
			}
		}

        // 排序
		AnnotationAwareOrderComparator.sort(allListeners);
		if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
		    // 清楚原本的 listeners 容器,并重新添加
			retriever.applicationListeners.clear();
			retriever.applicationListeners.addAll(allListeners);
		}
		return allListeners;
	}

看到上面得代码,我已经到了第 7 步伐,那就是 supportsEvent 方法。这个方法是抽象出来的,在其父类 AbstractApplicationEventMulticaster。在这个方法里面有点复杂,但是值得去深究一下,来,跟你细品一下。

关于上面的里面我需要解释几个点

  1. GenericApplicationListener 是 SmartApplicationListener 与 ApplicationListener 的实现类。目的是为了公开更多的元数据,如受支持的事件和源类型。
  2. GenericApplicationListenerAdapter 是一个适配器,内含代理类以及其感兴趣的事件类型。下面如何根据监听器获取监听器感兴趣类型的代码
    	public GenericApplicationListenerAdapter(ApplicationListener<?> delegate) {
    		this.delegate = (ApplicationListener<ApplicationEvent>) delegate;
    		this.declaredEventType = resolveDeclaredEventType(this.delegate);
    	}
    	
        private static ResolvableType resolveDeclaredEventType(ApplicationListener<ApplicationEvent> listener) {
            // 获取 event 的类型
    		ResolvableType declaredEventType = resolveDeclaredEventType(listener.getClass());
    		// 判断 ApplicationEvent 是否转为 event
    		if (declaredEventType == null || declaredEventType.isAssignableFrom(ApplicationEvent.class)) {
    		    // 确定监听器是不是AOP代理。
    			Class<?> targetClass = AopUtils.getTargetClass(listener);
    			// 如果 targetClass 和 listener.getClass 不是同一个
    			if (targetClass != listener.getClass()) {
    			    // 如果是话需要递归查找
    				declaredEventType = resolveDeclaredEventType(targetClass);
    			}
    		}
    		return declaredEventType;
    	}
    	
    	// 查找 eevent 的类型
    	static ResolvableType resolveDeclaredEventType(Class<?> listenerType) {
    	    // 查找缓存
    		ResolvableType eventType = eventTypeCache.get(listenerType);
    		if (eventType == null) {
    		    // 根据 ResolvableType 处理泛型参数找到感兴趣的事件,并加入缓存
    			eventType = ResolvableType.forClass(listenerType).as(ApplicationListener.class).getGeneric();
    			eventTypeCache.put(listenerType, eventType);
    		}
    		return (eventType != ResolvableType.NONE ? eventType : null);
    	}
    
  3. supportsEventType(ResolvableType resolvableType) 方法是用来检测是否目标 Event 是监听器支持的所支持的
  4. supportsSourceType(Class<?> sourceType) 方法是用来检测是否目标 Event 是监听器支持的所支持的

好,问题点都说得差不多了。我们来看第 7 步的代码

	protected boolean supportsEvent(
			ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
        // 查看是否是 GenericApplicationListener 的子类,做对应操作  
		GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
				(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
		// 通过 supportsEventType 和 supportsSourceType 来查看是否 event 是监听器所感兴趣的
		return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
	}

到目前为止我们基本上已经讲完了从启动到各个不同阶段广播事件到各个不同的监听器。不知道我说清楚了没有,噗!!!

总结

工作之余,断断续续把这个文章给写完了。有机会的话,我会写一篇模仿 Spring Boot 的监听器来写一个 demo 来巩固今天我自己的学习知识。

如果对于你们的学习以及成长有所帮助的话,{今天点赞加关注,未来一起去进步}!❤️