[Серия исходных решений Spring Boot] Режим прослушивания в Spring Boot

задняя часть
[Серия исходных решений Spring Boot] Режим прослушивания в Spring Boot

Тема этой статьи

В этой статье я хочу записать то, что я только что узнал — шаблон слушателя, и я хочу рассказать о реализации шаблона слушателя в Spring Boot. Эта статья будет разделена на несколько разделов для написания:
  1. Расскажите о режиме мониторинга и сценариях его применения и напишите небольшой каштан
  2. Запишите применение шаблона слушателя в Spring Boot
  3. Напишите исходное решение Spring Boot о шаблоне прослушивателя

Пойдем!

режим слушателя

В шаблонах проектирования вы, возможно, слышали об одном из 23 шаблонов проектирования, шаблоне Observer, также известном как шаблон публикации-подписки. Полезность шаблона наблюдателя заключается в том, что когда цель (наблюдаемая) управляет всеми наблюдателями, которые зависят от нее, и активно отправляет уведомления при изменении своего собственного состояния. Обычно это достигается путем вызова методов, предоставляемых каждым наблюдателем. Итак, мы абстрагируем целевой объект и наблюдателя. Это помогает разделять модули, позволяет модулям сосредоточиться на реализации определенной функции и улучшает ремонтопригодность и возможность повторного использования системы.
Слушатель — это одна из реализаций шаблона наблюдателя, а также шаблон публикации-подписки. В объектно-ориентированном он разбирается на несколько типов, которые заключаются в следующем:

На приведенном выше рисунке реализация прослушивателя предназначена для разделения на три части: вещатель [Multicast], событие [Event] и слушатель [Listener]. Среди них слушатель и событие отделены друг от друга, и слушателю нужно только определить события, которые ему интересны для прослушивания, а вещатель отвечает за трансляцию события в соответствующий момент времени.

Давайте попрактикуемся с простым маленьким каштаном, чтобы объяснить.

каштан

Помню, в документальном фильме «Китайский повар» была сцена, которая произвела на меня глубокое впечатление. То есть, когда ингредиенты подготовлены, шеф-повар будет нести ответственность за эффективное развертывание каждого человека на кухне, ответственного за разные части, для работы в разное время, точно так же, как общая стратегия на поле боя. Например, повар прикажет некоторым людям мыть овощи, другим готовить овощи, некоторым мариновать мясо и т. д. Во всем процессе работа, за которую они отвечают, различна, и момент их выполнения также различен.

событие [событие]

Итак, мы извлекаем из него несколько событий [Event]
1. МойкаОвощейСобытие
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 "上菜事件";
    }
}

слушатель [ слушатель ]

После того, как команда написана, кто является лицом, контролирующим команду (исполняющим команду)? Конечно официанты, повара, помогают поварам. Затем вы можете использовать эти роли в качестве слушателей, прислушиваясь к командам шеф-повара. Их всех объединяет то, что они ждут команды, поэтому мы можем абстрагироваться от этого.

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

Вещатель [Многоканальный]

ок, вышеуказанное событие [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");
    }
}

тестовая демонстрация[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);
    }
}

Режим прослушивания в 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 открытый класс BlogApplication { public static void main(String[] args) { Контекст ConfigurableApplicationContext = SpringApplication.run(BlogApplication.class, args); // Добавляем слушателя в SpringApplication context.addApplicationListener (новый MyApplicationListener()); // публикация события context.publishEvent (новый MyApplicationEvent (новый объект ())); контекст.закрыть(); } } ````

Анализ исходного кода

Примечания к выпуску: Spring Boot 2.2.4.RELEASE

Инструмент отладки: идея

Во-первых, позвольте мне взглянуть на общий процесс

Прочитав приведенную выше диаграмму, я полагаю, что у вас есть определенное понимание всего процесса. Затем я расскажу о типах событий, которые транслирует Spring Boot.

в порядке. Начнем с исходного кода шаг за шагом.

Во-первых, наш стартовый класс начинается с SpringApplication.java. На следующем рисунке показан скриншот фрагмента кода метода SpringApplication.run().

Мы можем посмотреть на такой небольшой фрагмент кода. Этот код означает, что Spring транслирует ApplicationStartingEvent (событие запуска приложения), а шаг по удалению вас здесь — второй шаг.

	listeners.starting();

Затем мы начинаем анализировать исходный код вокруг этого кода. Копнув глубже, мы обнаружили, что этоSpringApplicationRunListenerДобрый. представлять,SpringApplicationRunListenerПо сути, это класс-слушатель, но также и прокси-класс. Во всем процессе Spring Boot именно прослушиватель получает уведомления о событиях из разных точек выполнения. Например, если Spring Boot вызывает событие запуска, оно может транслироваться напрямую путем запуска или запуска, что улучшает инкапсуляцию и уменьшает внешнее восприятие.

существуетSpringApplicationRunListenerВ начальном методе вызовитеSimpleApplicationEventMulticaster.SimpleApplicationEventMulticasterДостигнутоAbstractApplicationEventMulticaster(Обеспечивает основные операции над слушателями). а такжеSimpleApplicationEventMulticasterОн отвечает за трансляцию событий. Стоит отметить, что мы можемsetTaskExecutor()настроитьSimpleApplicationEventMulticasterизtaskExecutorсвойство, которое является точкой расширения.

попал внутрьSimpleApplicationEventMulticasterПозже мы увидим, как он публикует событие

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

Рассматриваемый нами метод resolveDefaultEventType отвечает за проверку типа события. После определения типа последующим слушателям удобно определить, принимать ли это событие. После определения типа входим в одноименный метод 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метод. Грубо говоря, этот метод заключается в поиске слушателя, соответствующего событию по заданным условиям.

	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) используется для определения того, поддерживается ли прослушивателем целевое событие.
  4. Метод supportsSourceType(Class> sourceType) используется для определения того, поддерживается ли прослушивателем целевое событие.

Хорошо, это в значительной степени суть вопроса. Давайте посмотрим на код для шага 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, чтобы написать демонстрацию, чтобы закрепить мои собственные знания сегодня.

Если это полезно для вашего обучения и роста, {Нравится и подписывайтесь сегодня, и вместе развивайтесь в будущем}! ❤️