设计模式第4式:观察者模式Spring事件监听机制

news2024/9/9 8:18:08

前言

观察者模式是一种非常重要的设计模式,在JDK和Spring源码中使用非常广泛,而且消息队列软件如kafka、rocketmq等也应用了观察者模式。那么我们就很有必要学习一下观察者模式了。

随后我们来看看大名鼎鼎的事件监听机制,它是基于观察者模式的,但是是由具体的事件来驱动系统的运行,事件监听机制在Spring源码中占有非常重要的位置。

正文

观察者模式是一对多关系,以及松耦合。下面我们跟着《Head First设计模式》的案例的实现,来体会该模式的好处。

气象站案例

现在要开发一个气象站系统,气象站系统包括3个部分:
1、气象站:获取实际气象数据的物理装置;
2、WeatherData对象:追踪来自气象站的数据,并更新布告板。WeatherData对象知道怎么跟气象站联系以获取更新的数据。WeatherData对象随即更新3个布告板的显示;
3、布告板:显示当前天气状况给用户;

我们先来看WeatherData类长什么样?

Class WeatherData {
	getTemperature(); // 获取温度 
	getHumidity(); // 获取湿度
	getPressure(); // 获取气压

	public void measurementsChanged() {
		// 一旦气象数据更新,此方法会被调用
		// 我们的代码加在这里
	}

先看一个错误示范:

Class WeatherData {
	getTemperature(); // 获取温度 
	getHumidity(); // 获取湿度
	getPressure(); // 获取气压

	public void measurementsChanged() {
		// 调用get方法获取数据
		float temp = getTemperature(); 
		float humidity = getHumidity();
		float pressure = getPressure(); 

		// 再更新3个布告板
		currentDisplay.updata(temp, humidity, pressure);
		statisticsDisplay.updata(temp, humidity, pressure);
		forecastDisplay.updata(temp, humidity, pressure);
	}

这种实现有什么不对吗?我们在更新布告板的时候,使用的是3个布告板的具体对象,我们针对了具体实现编程,这会导致我们以后在增加或删除布告板的时候必须修改程序。到此我们留个悬念,看怎么利用观察者模式来改造这个案例。

引入观察者模式

我们来回忆一下报纸杂志的订阅是怎么回事。
1、报社出版报纸
2、小明向某家报社订阅报纸,报社就会给小明送报纸
3、当小明不想再看报了,就取消订阅,报社则不再给小明送报
4、只要报社还在运营,就一直有人订阅或取消报纸

出版社 + 订阅者 = 观察者模式。我们来转换一下语言,出版者改成“主题”(Subject),订阅者改为“观察者”(Observer)。主题对象管理某些数据,当主题内的数据改变,就会通知观察者。观察者模式定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。下面看下观察者模式的类图:
在这里插入图片描述
那么,松耦合到底有什么样的威力?当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。观察者模式就提供了一种对象设计,让主题和观察者之间松耦合。主题只知道观察者实现了Observer接口,它不需要知道观察者的具体类是谁,以及其他细节。我们可以随时新增观察者,因为主题唯一依赖的是一个实现了Observer接口的对象列表,在运行时我们可以替换或删除某些观察者。当有新的观察者出现时,主题无需改代码,只需要观察者实现接口,然后注册为观察者即可。

我们可以独立复用主题或观察者,如果我们在其他地方需要使用主题或观察者类,可以轻松复用,因为二者松耦合。

我们可以独立改变主题或观察者,并不会影响另一方。因为它们是松耦合的,所以只要它们的接口仍被遵从,我们就可以自由的改变它们。

设计原则:为了交互对象之间的松耦合设计而努力

改造气象站案例

下面这个图是利用观察者模式改造过的设计图。
在这里插入图片描述
1、我们从建立接口开始,主题接口最主要的有3个接口:注册和删除观察者,主题变化后通知所有观察者。

public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}

观察者接口主要的方法只有一个,就是接收主题的状态变化并作出相关的动作,这里的观察者就是接口主题发来的天气数据后,展示到布告板。

public interface Observer {
    public void update(float temp, float humidity, float pressure);
}

还有一个展示接口,属于辅助接口,不重要。

public interface DisplayElement {
    public void display();
}

2、再来实现主题接口,WeatherData类实现接口,并持有一个观察者对象的List,一旦主题有变化,就可以遍历List并调用每一个观察者的update方法,达到通知的目的。

public class WeatherData implements Subject{
    private float temp;
    private float humidity;
    private float pressure;
    
    private List<Observer> observers;

    public WeatherData() {
        this.observers = new ArrayList<>();
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temp, humidity, pressure);
        }
    }
    
    // 这里就是最初预留给我们实现的方法
    public void measurementsChanged() {
        notifyObservers();
    }
    
    // 模拟数据变化
    public void setData(float temp, float humidity, float pressure) {
        this.temp = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

3、我们再来实现观察者,下面是众多观察者中的一个。观察者除了要实现Observer接口的方法外,还要将自己注册给主题,在这个例子中,观察者持有一个主题对象,并在实例化时将自己注册到主题中去。其实这个注册的动作可以有很多形式,目的都是将观察者注册进主题的List列表中。

public class CurrentDisplay implements Observer, DisplayElement{
    private float temp;
    private float humidity;

    private Subject subject;
    // 通过构造方法将自己注册到Subject
    public CurrentDisplay(Subject s) {
        this.subject = s;
        subject.registerObserver(this);
    }
    
    @Override
    public void display() {
        System.out.println("temp: " + this.temp + ", humidity: " + this.humidity);
    }

    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temp = temp;
        this.humidity = humidity;
    }
}

观察者模式在JDK中的应用

JDK util包里自带了Observable主题类和Observer观察者接口,我们可以使用它们来实现观察者模式。但是在JDK9之后就不再推荐使用了,这里就不再深入介绍了,使用原理和上面的差不多,大家有兴趣可以看下。

事件监听机制是什么

软件开发中,触发某事件并对事件作出反应是非常常见的开发模式。它是基于观察者模式,加入了“事件”元素,主题发布事件,观察者(也可以叫监听者)监听特定的事件并作出反应。Java对此也提供了支持,java.util包里面提供了事件类和事件监听者接口,Spring对此做了很好的实现,待会来看。

public class EventObject implements java.io.Serializable {
    protected transient Object source;

    public EventObject(Object source) {
        if (source == null)
            throw new IllegalArgumentException("null source");

        this.source = source;
    }
}

EventObject类有一个Object类型的属性,一般传入发布事件的主题类对象。

public interface EventListener {
}

事件监听者接口空着,由子类实现。

事件监听机制在Spring中的应用

Spring在启动过程中会有很多节点,比如context准备启动了、context启动完成了、context启动失败了,Spring将这些节点都定义为“事件”。这些事件发生后,监听者就会收到通知,进而作出响应的动作。Spring的启动流程可以参考本人Spring源码解析系列,其中启动过程解析博客:IOC容器启动过程详解。

1、主题是谁

Spring提供了一个事件发布接口,我们都知道发布事件是主题(Subject)干的事情,那么在Spring中谁来扮演主题这个角色呢?

@FunctionalInterface
public interface ApplicationEventPublisher {
    void publishEvent(Object event);
}

答案是IOC容器本身,应用上下文ApplicationContext接口继承了ApplicationEventPublisher接口,并在实现类AbstractApplicationContext中实现了publishEvent抽象方法。

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
	private ApplicationEventMulticaster applicationEventMulticaster;

	@Override
	public void publishEvent(Object event) {
		publishEvent(event, null);
	}

	protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		...
		applicationEvent = (ApplicationEvent) event;
		// 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);
		}
		...
	}
}

我们通过看源码,发现IOC容器作为主题,调用publishEvent方法发布事件,publishEvent又调用了内部的同名重载方法,最终将事件发布委托给了ApplicationEventMulticaster接口的multicastEvent方法。这种委托方式在Spring中非常常见,毕竟Spring启动有很多步骤,如果事情都是自己做,那么代码将会又臭又长,IOC容器只关注主体流程,细枝末节的处理就交给其他类了。

我们再来看看事件发布器是怎么初始化的,在IOC容器启动的refresh方法(在AbstractApplicationContext类中)中有一步是专门初始化事件发布器的。

public void refresh() {
	...
	// Initialize event multicaster for this context.
	initApplicationEventMulticaster();
	...
}

方法也比较简单,如果容器中已经有事件发布器了,就从容器中拿出来给属性applicationEventMulticaster赋值;如果容器中没有,则new一个SimpleApplicationEventMulticaster对象出来。上面讲的委托实际上就是委托给SimpleApplicationEventMulticaster实现类了。

protected void initApplicationEventMulticaster() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
			this.applicationEventMulticaster =
					beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
		}
		else {
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
			beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
		}
	}

再来看一眼SimpleApplicationEventMulticaster中的方法multicastEvent方法,看到方法中的for循环了吗,这里就是遍历所有观察者(监听者),然后调用它们的监听方法。看到这里,有没有点感觉?事件监听机制的底层还是观察者模式。

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

2、事件

Java中的事件类是EventObject,它有一个重要属性source保存是哪个类发布的事件。

public class EventObject implements java.io.Serializable {
    /**
     * The object on which the Event initially occurred.
     */
    protected transient Object source;
}

Spring中的事件类是ApplicationEvent,加了一个时间戳属性

public abstract class ApplicationEvent extends EventObject {
	private final long timestamp;

	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}
}

3、监听器

Java中的监听器只是一个空接口。

public interface EventListener {
}

Spring中的监听器是ApplicationListener,它继承了Java中的EventListener接口。它拥有一个泛型参数,只接收事件类型,表示监听哪一类事件;它有一个抽象方法,用于实现监听到事件发生后的处理逻辑,实现类重点实现了这个方法;它还是一个函数式接口。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	void onApplicationEvent(E event);
}

再来介绍2个子接口,它们继承ApplicationListener接口。SmartApplicationListener接口设计了2个方法,这2个方法用来判断监听器本身是否支持给定的eventType或sourceType。这2个方法有什么用呢?比如在发布事件A时,需要先筛选出监听事件A的所有监听器,筛选过程就用到这2个方法。

public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
	/**
	 * Determine whether this listener actually supports the given event type.
	 */
	boolean supportsEventType(Class<? extends ApplicationEvent> eventType);

	/**
	 * Determine whether this listener actually supports the given source type.
	 */
	default boolean supportsSourceType(@Nullable Class<?> sourceType) {
		return true;
	}
	。。。
}

子接口GenericApplicationListener继续关注supportsEventType方法。

public interface GenericApplicationListener extends SmartApplicationListener {
	@Override
	default boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
		return supportsEventType(ResolvableType.forClass(eventType));
	}

	boolean supportsEventType(ResolvableType eventType);
}

子类GenericApplicationListenerAdapter实现了supportsEventType方法,用来筛选监听器,下面小节会讲。注意这个类是一个监听器适配器类,它用于解析监听器对象。

public class GenericApplicationListenerAdapter implements GenericApplicationListener {
	// 2个属性,一个是监听器对象,另一个是监听的事件类型
	private final ApplicationListener<ApplicationEvent> delegate;
	private final ResolvableType declaredEventType;

	// 构造器将传入的listener进行解析,填充2个属性值
	public GenericApplicationListenerAdapter(ApplicationListener<?> delegate) {
		this.delegate = (ApplicationListener<ApplicationEvent>) delegate;
		this.declaredEventType = resolveDeclaredEventType(this.delegate);
	}

	@Override
	public boolean supportsEventType(ResolvableType eventType) {
		// 如果监听器实现了父接口,就交给实现类处理
		if (this.delegate instanceof GenericApplicationListener) {
			return ((GenericApplicationListener) this.delegate).supportsEventType(eventType);
		}
		// 如果监听器实现了父接口,就交给实现类处理
		else if (this.delegate instanceof SmartApplicationListener) {
			Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.resolve();
			return (eventClass != null && ((SmartApplicationListener) this.delegate).supportsEventType(eventClass));
		}
		// 如果监听事件为空,或者当前监听器监听的事件类型是给定事件类型的父类
		// A.isAssignableFrom(B)表示A和B是同一个类(接口)或者A是B的父类(父接口)
		else {
			return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
		}
	}
	。。。
}

4、事件发布器

我们来看下Spring中的监听器们是怎么被注册到容器中的,以及事件是怎么发布的。上文我们提到了Spring其实是将事件发布动作委托给了SimpleApplicationEventMulticaster类,就来看看被委托的类是什么样的。

顶层接口设计了一些增删监听器的方法,以及发布事件的方法。

public interface ApplicationEventMulticaster {
	void addApplicationListener(ApplicationListener<?> listener);
	void addApplicationListenerBean(String listenerBeanName);

	void removeApplicationListener(ApplicationListener<?> listener);
	void removeApplicationListenerBean(String listenerBeanName);
	void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate);
	void removeApplicationListenerBeans(Predicate<String> predicate);
	void removeAllListeners();

	void multicastEvent(ApplicationEvent event);
	void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);

}

抽象实现类实现了增删监听器的方法,实质是将监听器对象放进内部类的Set集合中。

public abstract class AbstractApplicationEventMulticaster
		implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
			@Override
	public void addApplicationListener(ApplicationListener<?> listener) {
		synchronized (this.defaultRetriever) {
			// 添加监听器到内部类
			this.defaultRetriever.applicationListeners.add(listener);
		}
	}
}

最底层的类继承了上面的抽象类并实现了发布事件的方法。下面摘取了部分主要代码。

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
	@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}
	
	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		// 获取事件的类型,即事件对象的一些属性
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		// 根据事件对象及类型获取相关的监听器,它最终调用retrieveApplicationListeners方法
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			...
			invokeListener(listener, event);
		}
	}
}

getApplicationListeners方法在其父类AbstractApplicationEventMulticaster中,并最终调用了retrieveApplicationListeners方法。supportsEvent方法就是用来筛选的,用到的GenericApplicationListenerAdapter类在上一小节也讲过。

private Collection<ApplicationListener<?>> retrieveApplicationListeners(...) {
	// 遍历内部类中的Set集合,找到匹配的监听器
	for (ApplicationListener<?> listener : listeners) {
		if (supportsEvent(listener, eventType, sourceType)) {
			...
			allListeners.add(listener);
		}
	}
	return allListeners;
}

protected boolean supportsEvent(
		ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
	// 这里先判断监听器的类型,如果是GenericApplicationListener类型,就直接转换成该类型,否则用GenericApplicationListenerAdapter类型包装一下。
	GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
			(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
	// 返回2个类型匹配方法的结果
	return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

筛选到对应的监听器后,就该发布事件了,我们再回到multicastEvent方法的for循环。

for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
	// 触发监听器
	invokeListener(listener, event);
}

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
	...
	else {
		// Spring很喜欢搞这样的方法嵌套
		doInvokeListener(listener, event);
	}
}

@SuppressWarnings({"rawtypes", "unchecked"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
	try {
		// 终于到头了,这里就是调用监听器的onApplicationEvent方法
		listener.onApplicationEvent(event);
	}
	catch (ClassCastException ex) {
		...
	}

SpringBoot中的事件监听机制

SpringBoot使用的事件机制核心还是上面讲到的Spring这一套事件监听机制。我们使用SpringBoot事件机制时,定义好了监听器后,将其类型在spring.factories文件中,SpringBoot在启动时会读取这个文件。SpringBoot的事件监听机制只是在Spring的基础上,加上了配置文件读取这部分。我们来看源码。

1、SPI机制

SPI机制说白了就是将使用到的类写在spring.factories文件中,再配合一个方法就能将类加载到容器中。我们从SpringBoot的启动类的启动方法run开始。

public class SpringApplication {
	// run方法传入main方法所在的类,及main方法参数
	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class[]{primarySource}, args);
    }
	// 实例化本身,并调用重载的run方法
	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
    }

我们再来看下实例化做了什么,它从spring.factories文件中拿到key为ApplicationListener的所有配置值,实例化后放进属性listeners中。

public class SpringApplication {
	private List<ApplicationListener<?>> listeners;
	// 构造方法
	public SpringApplication(Class<?>... primarySources) {
	    this((ResourceLoader)null, primarySources);
	}
	
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		// 设置listeners属性
		this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
	}
}

好神奇啊,SpringBoot一启动就能把所有的监听器加载到内存中。起作用的就是下面这个方法,传入一个类型A,它就会找到所有spring.factories文件中以类型A为key的所有值,并实例化。

this.getSpringFactoriesInstances(ApplicationListener.class);

spring.factories配置文件很简单,就是key-value键值对。key就是顶层接口全类名,value就是一堆实现类的全类名,以逗号隔开。仔细看,下面这个监听器接口就是Spring的接口。

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener

2、发布事件

实例化后,再回到run方法。下面这个就是SpringBoot的启动方法,我们只摘取一小部分和事件发布相关的方法。

public ConfigurableApplicationContext run(String... args) {
	...
	// 使用SPI获取
	SpringApplicationRunListeners listeners = getRunListeners(args);
	// 发布开始启动事件
	listeners.starting(bootstrapContext, this.mainApplicationClass);
	try {
		...
		// 发布已启动事件
		listeners.started(context, timeTakenToStartup);
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		// 发布启动失败事件
		handleRunFailure(context, ex, listeners);
		throw new IllegalStateException(ex);
	}
}

我们看到,在run方法中,要发布很多事件都是由SpringApplicationRunListeners来执行的,我们就来看看它的源码。它主要是使用了函数式编程来实现

class SpringApplicationRunListeners {
	// list属性
	private final List<SpringApplicationRunListener> listeners;
	// 下面设计了一堆方法,定义了SpringBoot启动的不同阶段,它们最终都调用了doWithListeners方法
	void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
		doWithListeners((listener) -> listener.starting(bootstrapContext));
	}

	void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
		doWithListeners((listener) -> listener.environmentPrepared(bootstrapContext, environment));
	}

	void contextPrepared(ConfigurableApplicationContext context) {
		doWithListeners((listener) -> listener.contextPrepared(context));
	}

	void contextLoaded(ConfigurableApplicationContext context) {
		doWithListeners((listener) -> listener.contextLoaded(context));
	}

	void started(ConfigurableApplicationContext context, Duration timeTaken) {
		doWithListeners((listener) -> listener.started(context, timeTaken));
	}

	void ready(ConfigurableApplicationContext context, Duration timeTaken) {
		doWithListeners((listener) -> listener.ready(context, timeTaken));
	}

	void failed(ConfigurableApplicationContext context, Throwable exception) {
		doWithListeners((listener) -> callFailedListener());
	}

	// 这是个模板方法,使用了函数式编程的思想,第2个参数是重点
	private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
			Consumer<StartupStep> stepAction) {
		// 这里循环执行SpringApplicationRunListener中的lambda表达式方法
		this.listeners.forEach(listenerAction);
	}
}

我们再来看看属性List<SpringApplicationRunListener> listeners中的泛型,注意它没有s。它是个接口,同样规定了SpringBoot启动的不同阶段,抽象方法在实现类中实现。

public interface SpringApplicationRunListener {

	default void starting(ConfigurableBootstrapContext bootstrapContext) {
	}

	default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
			ConfigurableEnvironment environment) {
	}

	default void contextPrepared(ConfigurableApplicationContext context) {
	}

	default void contextLoaded(ConfigurableApplicationContext context) {
	}

	default void started(ConfigurableApplicationContext context, Duration timeTaken) {
	}

	default void ready(ConfigurableApplicationContext context, Duration timeTaken) {
	}

	default void failed(ConfigurableApplicationContext context, Throwable exception) {
	}
}

实现类EventPublishingRunListener 完全就是调用Spring那一套事件发布机制了。

class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
	// 这个属性就是Spring的事件发布器
	private final SimpleApplicationEventMulticaster initialMulticaster;
	// 构造方法实例化事件发布器
	EventPublishingRunListener(SpringApplication application, String[] args) {
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
	}
	// 下面的一系列方法就是发布不同的事件了
	@Override
	public void starting(ConfigurableBootstrapContext bootstrapContext) {
		multicastInitialEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
	}

	@Override
	public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
			ConfigurableEnvironment environment) {
		multicastInitialEvent(
				new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
	}

	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {
		multicastInitialEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
	}

	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
		multicastInitialEvent(new ApplicationPreparedEvent(this.application, this.args, context));
	}

	@Override
	public void started(ConfigurableApplicationContext context, Duration timeTaken) {
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, timeTaken));
		AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
	}

	@Override
	public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
		context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));
		AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
	}

	@Override
	public void failed(ConfigurableApplicationContext context, Throwable exception) {
		context.publishEvent(event);
	}
	// 重点方法
	private void multicastInitialEvent(ApplicationEvent event) {
		// 看下这个发布事件的方法,就是Spring中的方法
		this.initialMulticaster.multicastEvent(event);
	}
}

至此,大家是不是有一种豁然开朗的感觉?那么我们再回到run方法,看下是怎么获取事件发布器的。

SpringApplicationRunListeners listeners = getRunListeners(args);
private SpringApplicationRunListeners getRunListeners(String[] args) {
	// SPI获取实例
	List<SpringApplicationRunListener> listeners = getSpringFactoriesInstances(SpringApplicationRunListener.class,
			argumentResolver);
	// 再封装一层返回
	return new SpringApplicationRunListeners(logger, listeners, this.applicationStartup);
}

下面就是spring.factories的内容,我们看实现类就是上面说的EventPublishingRunListener。

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

总结

我们先从改造一个实际案例来引出观察者模式,随即又在观察者模式上进一步引出事件发布机制,接着我们又分别从源码角度深度解析了事件发布机制的原理。这一系列讲解下来,相信大家对观察者模式和事件监听机制的理解会更加透彻。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/183105.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

直波导与微环的耦合——Lumerical仿真1

微环与直波导的耦合的Lumerical仿真的一个记录&#xff0c;包括仿真步骤和一些问题的探究&#xff0c;参考自https://www.bilibili.com/video/BV1tF411z714。 &#x1f381;附Lumerical仿真文件 Lumerical第一个仿真一、建立结构1、放置微环结构2、修改结构二、光源1、放置光源…

React是不是MVVM架构?

首先说结论&#xff1a;不是 一、MVVM Model-View-ViewModel&#xff1a;一句话概括MVVM&#xff0c;操作数据&#xff0c;就是操作视图&#xff0c;就是操作DOM。开发者只需要完成包含申明绑定的视图模板&#xff0c;编写ViewModel中业务数据变更逻辑&#xff0c;View层则完…

MySQL(六):redo日志——保证事务的持久性

目录一、redo日志的基本介绍1.1 什么是redo日志1.2 redo日志的格式1.3 redo日志的类型1.4 Mini-Transaction二、redo日志的写入过程2.1 redo log block2.2 redo日志缓冲区2.3 将redo日志写入log buffer2.3 redo日志刷新到磁盘2.5 redo日志文件组2.6 redo日志文件格式2.7 log se…

【MySQL进阶】MySQL触发器详解

序号系列文章7【MySQL基础】运算符及相关函数详解8【MySQL基础】MySQL多表操作详解9【MySQL进阶】MySQL事务详解10【MySQL进阶】MySQL视图详解文章目录前言1&#xff0c;触发器1.1&#xff0c;触发器概述1.2&#xff0c;触发器使用环境2&#xff0c;触发器基本操作2.1&#xff…

CentOS7 安装单机 Kafka

一、单机安装 1、上传压缩文件到服务器、解压 tar -zxvf kafka_2.13-3.3.2.tgz -C /usr/local #解压到 usr/local目录下 进入解压目录下 更名kafka mv kafka_2.13-3.3.2/ kafka-3.3.2 2、配置环境变量 vim /etc/profile export KAFKA_HOME/usr/local/kafka-3.3.2export PATH$PA…

MySQL学习记录(9)InnoDB存储引擎

文章目录6、InnoDB存储引擎6.1、逻辑存储结构6.2、架构6.2.1、概述6.2.2、内存结构6.2.3、磁盘结构6.2.4、后台线程6.3、事务原理6.3.1、事务基础6.3.2、redo log日志6.3.3、undo log日志6.4、MVCC6.4.1、基本概念6.4.2、记录中隐藏字段6.4.3、undo log日志6.4.4、readview6.4.…

【Pytorch项目实战】之图像分类与识别:手写数字识别(MNIST)、普适物体识别(CIFAR-10)

文章目录图像分类与识别&#xff08;一&#xff09;实战&#xff1a;基于CNN的手写数字识别&#xff08;数据集&#xff1a;MNIST&#xff09;&#xff08;二&#xff09;实战&#xff1a;基于CNN的图像分类&#xff08;数据集&#xff1a;CIFAR-10&#xff09;图像分类与识别 …

Lua 函数 - 可变参数

Lua 函数 - 可变参数 参考至菜鸟教程。 Lua函数可以接收可变数目的参数&#xff0c;和C语言类似&#xff0c;在函数参数列表中使用三点...表示函数有可变的参数。 function add(...) local s 0 for i, v in ipairs{...} do --> {...} 表示一个由所有变长参数构成的数…

模拟实现C库函数(2)

"烦恼无影踪,丢宇宙~"上一篇的模拟实现了好几个库函数,strlen\strcpy\memcpy\memmove,那么这一篇又会增加几个常用C库函数的模拟实现 memset\itoa\atoi。一、memsetmemset - fill memory with a constant byte#include <string.h>void *memset(void *s, int c,…

机器自动翻译古文拼音 - 十大宋词 - 江城子·乙卯正月二十日夜记梦 苏轼

江城子乙卯正月二十日夜记梦 宋苏轼 十年生死两茫茫&#xff0c;不思量&#xff0c;自难忘。 千里孤坟&#xff0c;无处话凄凉。 纵使相逢应不识&#xff0c;尘满面&#xff0c;鬓如霜。 夜来幽梦忽还乡&#xff0c;小轩窗&#xff0c;正梳妆。 相顾无言&#xff0c;惟有泪千…

uniapp使用及踩坑项目记录

环境准备 下载 HBuilderX 使用命令行创建项目&#xff1a; 一些常识准备 响应式单位rpx 当设计稿宽度为750px的时&#xff0c;1rpx1px。 uniapp中vue文件style不用添加scoped 打包成h5端的时候自动添加上去&#xff0c;打包成 微信小程序端 不需要添加 scoped。 图片的…

SpringDataJpa set()方法自动保存失效

问题描述&#xff1a;springdatajpa支持直接操作对象设置属性进行更新数据库记录的方式&#xff0c;正常情况下&#xff0c;get()得到的对象直接进行set后&#xff0c;即使不进行save操作&#xff0c;也将自动更新数据记录&#xff0c;将改动持久化到数据库中&#xff0c;但这里…

20230126使AIO-3568J开发板在原厂Android11下跑起来

20230126使AIO-3568J开发板在原厂Android11下跑起来 2023/1/26 18:22 1、前提 2、修改dts设备树 3、适配板子的dts 4、&#xff08;修改uboot&#xff09;编译系统烧入固件验证 前提 因源码是直接使用原厂的SDK&#xff0c;没有使用firefly配套的SDK源码&#xff0c;所以手上这…

Linux安装mongodb企业版集群(分片集群)

目录 一、mongodb分片集群三种角色 二、安装 1、准备工作 2、安装 configsvr配置 router配置 shard配置 三、测试 四、整合Springboot 一、mongodb分片集群三种角色 router角色&#xff1a; mongodb的路由&#xff0c;提供入口&#xff0c;使得分片集群对外透明&…

【目标检测论文解读复现NO.27】基于改进YOLOv5的螺纹钢表面缺陷检测

前言此前出了目标改进算法专栏&#xff0c;但是对于应用于什么场景&#xff0c;需要什么改进方法对应与自己的应用场景有效果&#xff0c;并且多少改进点能发什么水平的文章&#xff0c;为解决大家的困惑&#xff0c;此系列文章旨在给大家解读最新目标检测算法论文&#xff0c;…

【工程化之路】Node require 正解

require 实现原理 流程概述 步骤1&#xff1a;尝试执行代码require("./1"). 开始调用方法require.步骤2&#xff1a;此时会得到filename&#xff0c;根据filename 会判断缓存中是否已经加载模块&#xff0c;如果加载完毕直接返回&#xff0c;反之继续执行步骤3&…

python图像处理(laplacian算子)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 和之前的prewitt算子、sobel算子不同,laplacian算子更适合检测一些孤立点、短线段的边缘。因此,它对噪声比较敏感,输入的图像一定要做好噪声的处理工作。同时,laplacian算子设计…

Leetcode 03. 无重复字符的最长子串 [C语言]

目录题目思路1代码1结果1思路2代码2结果2该文章只是用于记录考研复试刷题题目 Leetcode 03: 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”&#xff0c;所…

尚医通-OAuth2-微信登录接口开发(三十一)

目录&#xff1a; &#xff08;1&#xff09;微信登录-OAuth2介绍 &#xff08;2&#xff09;前台用户系统-微信登录-准备工作 &#xff08;3&#xff09;微信登录-生成微信二维码-接口开发 &#xff08;4&#xff09;微信登录-生成验证码-前端整合 &#xff08;5&#xf…

Telerik DevCraft Ultimate R1 2023

Telerik DevCraft Ultimate R1 2023 Kendo UI R1 2023-添加新的Chip和ChipList组件。 KendoReact R1 2023&#xff08;v5.11.0&#xff09;-新的PDFViewer组件允许用户直接在应用程序中查看PDF文档。 Telerik JustLock R1 2023-Visual Studio快速操作菜单现在可以在创建通用…