Eine kurze Analyse des Spring Boot-Ereignismechanismus

1. Übersicht

Unter den Entwurfsmustern ist das Beobachtermuster ein häufig verwendetes Entwurfsmuster. Wikipedia erklärt es wie folgt:

 Das Beobachtermuster ist eine Art Software-Entwurfsmuster. In diesem Muster verwaltet ein Zielobjekt alle von ihm abhängigen Beobachterobjekte und gibt aktiv Benachrichtigungen aus, wenn sich sein eigener Zustand ändert. Dies wird normalerweise durch den Aufruf von Methoden erreicht, die von jedem Beobachter bereitgestellt werden.

In unserer täglichen Geschäftsentwicklung spielt das Beobachtermuster für uns eine große Rolle bei der Realisierung von Geschäftsentkopplung, Parameterübertragung usw. Nehmen Sie als Beispiel das Benutzerregistrierungsszenario. Angenommen, nach Abschluss der Benutzerregistrierung müssen E-Mails, Gutscheine usw. an den Benutzer gesendet werden, wie in der folgenden Abbildung dargestellt:

Bild

Bild

  • Nachdem UserService seine eigene Benutzerregistrierungslogik abgeschlossen hat, muss er nur noch ein UserRegisterEvent-Ereignis veröffentlichen, ohne auf andere Erweiterungslogiken zu achten.

  • Andere Dienste können das UserRegisterEvent-Ereignis selbst abonnieren, um benutzerdefinierte Erweiterungslogik zu implementieren.

Hinweis: Das Publish-Subscribe-Muster gehört im weitesten Sinne zum Beobachtermuster.

Im Beobachtermuster muss der Beobachter das Zielereignis direkt abonnieren. Nachdem das Ziel ein Ereignis gesendet hat, bei dem sich der Inhalt geändert hat, empfängt es das Ereignis direkt und antwortet.

 ╭─────────────╮  Fire Event  ╭──────────────╮
 │             │─────────────>│              │
 │   Subject   │              │   Observer   │
 │             │<─────────────│              │
 ╰─────────────╯  Subscribe   ╰──────────────╯

Beim Publish-Subscribe-Modell gibt es einen zusätzlichen Veröffentlichungskanal zwischen dem Herausgeber und dem Abonnenten; einerseits werden Ereignisse vom Herausgeber empfangen, andererseits werden Ereignisse an die Abonnenten veröffentlicht; der Abonnent muss sich anmelden vom Ereigniskanal zum Ereignis, um zu vermeiden, dass der Herausgeber eine Abhängigkeitsbeziehung mit Abonnenten erstellt

 ╭─────────────╮                 ╭───────────────╮   Fire Event   ╭──────────────╮
 │             │  Publish Event  │               │───────────────>│              │
 │  Publisher  │────────────────>│ Event Channel │                │  Subscriber  │
 │             │                 │               │<───────────────│              │
 ╰─────────────╯                 ╰───────────────╯    Subscribe   ╰──────────────╯

Einfach ausgedrückt gehört der Publish-Subscribe-Modus im weitesten Sinne zum Beobachtermodus. Basierend auf dem Subjekt und dem Beobachter des Beobachtermodus wird der Ereigniskanal als Vermittler eingeführt, um ihn weiter zu entkoppeln.

2. Konzepte im Ereignismuster

  • Ereignisquelle : Der Auslöser des Ereignisses, z. B. die Registrierung von Benutzerinformationen, deren Speicherung in der Datenbank und die Veröffentlichung von „Benutzer XX hat sich erfolgreich registriert“.

  • Ereignis : Ein Objekt, das beschreibt, was passiert ist, zum Beispiel: Ereignis „XX-Registrierung erfolgreich“.

  • Ereignis-Listener : Wenn das Ereignis eintritt, werden einige Verarbeitungsschritte ausgeführt, z. B. das Versenden von E-Mails nach erfolgreicher Registrierung, das Verschenken von Punkten, das Ausstellen von Gutscheinen usw.

3. Schritte zur Verwendung von Frühlingsereignissen

  • Definieren Sie Ereignisse

    Benutzerdefinierte Ereignisse müssen die ApplicationEvent-Klasse erben, um benutzerdefinierte Ereignisse zu implementieren. Darüber hinaus kann die Ereignisquelle über ihre  source Eigenschaften und timestamp die Ereigniszeit über die Eigenschaften ermittelt werden.

  • Zuhörer definieren

    Benutzerdefinierte Ereignis-Listener müssen die ApplicationListener-Schnittstelle und die onApplicationEvent-Methode implementieren, um relevante Ereignisse zu verarbeiten.

  • Erstellen Sie einen Event-Broadcaster

    Erstellen Sie einen Event-Broadcaster, um die ApplicationEventMulticaster-Schnittstelle zu implementieren, oder Sie können den von Spring definierten SimpleApplicationEventMulticaster verwenden:

    ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();
  • Registrieren Sie einen Ereignis-Listener beim Sender

    Registrieren Sie den Ereignis-Listener beim Broadcaster ApplicationEventMulticaster.

    applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreaterListener());
  • Veröffentlichen Sie Veranstaltungen über den Sender

    Um ein Ereignis zu übertragen, rufen Sie die Methode ApplicationEventMulticaster#multicastEvent auf, um das Ereignis zu übertragen. Zu diesem Zeitpunkt verarbeitet der Listener im Broadcaster, der an diesem Ereignis interessiert ist, das Ereignis.

    applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1L));

4. Anwendung

4.1 Schnittstellenorientierter Ansatz

Fall: Implementieren Sie die Funktion zum Veröffentlichen von Ereignissen nach erfolgreicher Benutzerregistrierung und zum anschließenden Senden von E-Mails im Listener.

Benutzerregistrierungsveranstaltung:

Erstellen Sie die Ereignisklasse UserRegisterEvent, erben Sie die Klasse ApplicationEvent und registrieren Sie Benutzerereignisse. Code wie folgt anzeigen:

public class UserRegistryEvent extends ApplicationEvent {
    private String userName;
    public UserRegistryEvent(Object source, String userName) {
        super(source);
        this.userName = userName;
    }

    public String getUserName() {
        return userName;
    }
}

E-Mail-Listener senden:

Erstellen Sie die SendEmailListener-Klasse und den Postfachdienst. Code wie folgt anzeigen:

@Component
public class SendEmailListener implements ApplicationListener<UserRegistryEvent> {
    Logger LOGGER = LoggerFactory.getLogger(SendEmailListener.class);

    @Override
    public void onApplicationEvent(UserRegistryEvent event) {
        LOGGER.info("给用户{}发送注册成功邮件!", event.getUserName());
    }
}

Beachten:

  • Implementieren Sie die ApplicationListener-Schnittstelle und  E legen Sie interessante Ereignisse über generische Elemente fest, z. B. UserRegistryEvent.

  • Implementieren Sie  #onApplicationEvent(E event) die Methode, um eine benutzerdefinierte Verarbeitung für das überwachte UserRegisterEvent-Ereignis durchzuführen.

Benutzerregistrierungsdienst: Registrierungsfunktion + Veröffentlichung eines Benutzerregistrierungsereignisses

Erstellen Sie die UserRegisterService-Klasse, user Service. Code wie folgt anzeigen:

@Service
@Slf4j
public class UserRegisterService implements ApplicationEventPublisherAware {
    private ApplicationEventPublisher applicationEventPublisher;

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

    public void registryUser(String userName) {
        // 用户注册(入库等)
        log.info("用户{}注册成功", userName);
        applicationEventPublisher.publishEvent(new UserRegistryEvent(this, userName));
        //applicationEventPublisher.publishEvent(event);
    }

}

Beachten:

  • Die ApplicationEventPublisherAware-Schnittstelle ist oben implementiert. Der Spring-Container injiziert den ApplicationEventPublisher über setApplicationEventPublisher, und dann können wir ihn zum Veröffentlichen von Ereignissen verwenden.

  • Rufen Sie nach dem Ausführen der Registrierungslogik die #publishEvent(ApplicationEvent event)Methode [ ] von ApplicationEventPublisher auf, um das Ereignis [UserRegisterEvent] zu veröffentlichen

überweisen:

@RestController
public class SpringEventController {
    @Autowired
    private UserRegisterService userRegisterService;

    @GetMapping("test-spring-event")
    public Object test(String name){
        LocalDateTime dateTime = LocalDateTime.now();
        userRegisterService.registryUser(name);
        return dateTime.toString() + ":spring";
    }

}

Gehen Sie zu http://localhost:12000/server/test-spring-event?name=name1

Ausgabe:

用户name1注册成功
给用户name1发送注册成功邮件!

Prinzip:
Während des Erstellungsprozesses einer Bean ermittelt der Spring-Container, ob die Bean vom Typ ApplicationListener ist, und registriert sie dann als Listener in AbstractApplicationContext#applicationEventMulticaster.

AbstractApplicationContext.java -》ApplicationEventPublisher
	@Override
	public void addApplicationListener(ApplicationListener<?> listener) {
		Assert.notNull(listener, "ApplicationListener must not be null");
		if (this.applicationEventMulticaster != null) {
			this.applicationEventMulticaster.addApplicationListener(listener); // 广播器中添加监听器
		}
		this.applicationListeners.add(listener);
	}

    // 发布事件
	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;
		if (event instanceof ApplicationEvent) {
			applicationEvent = (ApplicationEvent) event;
		}
		else {
			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);
			}
		}
	}

Der Quellcode für dieses Stück befindet sich in der folgenden Methode:

org.springframework.context.support.ApplicationListenerDetector#postProcessAfterInitialization

@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) {
		if (bean instanceof ApplicationListener) {
			// potentially not detected as a listener by getBeanNamesForType retrieval
			Boolean flag = this.singletonNames.get(beanName);
			if (Boolean.TRUE.equals(flag)) {
				// singleton bean (top-level or inner): register on the fly
				this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
			}
			else if (Boolean.FALSE.equals(flag)) {
				if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
					// inner bean with other scope - can't reliably process events
					logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
							"but is not reachable for event multicasting by its containing ApplicationContext " +
							"because it does not have singleton scope. Only top-level listener beans are allowed " +
							"to be of non-singleton scope.");
				}
				this.singletonNames.remove(beanName);
			}
		}
		return bean;
	}

4.2 Methoden für die @EventListener-Annotation

Sie können einen SpEL-Ausdruck über das Bedingungsattribut angeben. Wenn einer der Werte „true“, „on“, „yes“ oder „1“ zurückgegeben wird, wird das Ereignis verarbeitet, andernfalls nicht.

  	@EventListener(condition = "#userRegistryEvent.userName eq 'name2'")
    public void getCustomEvent(UserRegistryEvent userRegistryEvent) {
        LOGGER.info("EventListener 给用户{}发送注册邮件成功!", userRegistryEvent.getUserName());
    }

Besuchen Sie http://localhost:12000/server/test-spring-event?name=name1

Ausgabe:

用户name1注册成功
给用户name1发送注册成功邮件!

Besuchen Sie http://localhost:12000/server/test-spring-event?name=name2

Ausgabe:

用户name2注册成功
给用户name2发送注册成功邮件!
EventListener 给用户name2发送注册邮件成功!

Prinzip:

EventListenerMethodProcessor implementiert die SmartInitializingSingleton-Schnittstelle. Die afterSingletonsInstantiated-Methode in der SmartInitializingSingleton-Schnittstelle wird vom Spring-Container aufgerufen, nachdem alle Singleton-Beans erstellt wurden. Der Quellcode für die Verarbeitung von @EventListener-Annotationen im Frühjahr befindet sich in der folgenden Methode

org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated

public class EventListenerMethodProcessor
		implements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor {
	@Override
	public void afterSingletonsInstantiated() {
		...
        ...
        ...
					try {
						processBean(beanName, type); //bean
					}
					catch (Throwable ex) {
						throw new BeanInitializationException("Failed to process @EventListener " +
								"annotation on bean with name '" + beanName + "'", ex);
					}
				}
			}
		}
	}

	private void processBean(final String beanName, final Class<?> targetType) {
		if (!this.nonAnnotatedClasses.contains(targetType) &&
				AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
				!isSpringContainerClass(targetType)) {

			Map<Method, EventListener> annotatedMethods = null;
			try {
				annotatedMethods = MethodIntrospector.selectMethods(targetType,
						(MethodIntrospector.MetadataLookup<EventListener>) method ->
								AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
			}
			catch (Throwable ex) {
				// An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
				if (logger.isDebugEnabled()) {
					logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
				}
			}

			if (CollectionUtils.isEmpty(annotatedMethods)) {
				this.nonAnnotatedClasses.add(targetType);
				if (logger.isTraceEnabled()) {
					logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
				}
			}
			else {
				// Non-empty set of methods
				ConfigurableApplicationContext context = this.applicationContext;
				Assert.state(context != null, "No ApplicationContext set");
				List<EventListenerFactory> factories = this.eventListenerFactories;
				Assert.state(factories != null, "EventListenerFactory List not initialized");
				for (Method method : annotatedMethods.keySet()) {
					for (EventListenerFactory factory : factories) {
						if (factory.supportsMethod(method)) { // 此处,针对所有EventListener注解的方法,均返回true,
							Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
							ApplicationListener<?> applicationListener =
									factory.createApplicationListener(beanName, targetType, methodToUse);
							if (applicationListener instanceof ApplicationListenerMethodAdapter) {
								((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
							}
							context.addApplicationListener(applicationListener);// 往容器中注入监听器,同 接口方式
							break;
						}
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
							beanName + "': " + annotatedMethods);
				}
			}
		}
	}
}

4.3 Listener-Sortierung

Wenn für ein Ereignis mehrere Listener vorhanden sind, ist die Ausführungsreihenfolge der Listener standardmäßig ungeordnet, wir können jedoch die Reihenfolge für die Listener festlegen.

4.3.1 Implementieren Sie den Listener über die Schnittstelle:

Drei Möglichkeiten, die Listener-Reihenfolge anzugeben:

  • Implementieren Sie die org.springframework.core.Ordered-Schnittstelle #getOrder. Je kleiner der Rückgabewert, desto höher die Reihenfolge.

  • Implementieren Sie die org.springframework.core.PriorityOrdered-Schnittstelle #getOrder

  • Verwenden Sie die Annotation org.springframework.core.annotation.Order für die Klasse

4.3.2 Über @EventListener:

Sie können die Annotation @Order (Bestellwert) für die mit @EventListener gekennzeichnete Methode verwenden, um die Bestellung zu markieren.

4.4 Asynchroner Listener-Modus

Der Listener wird letztendlich über die interne Implementierung von ApplicationEventMulticaster aufgerufen. Die Standardimplementierungsklasse ist SimpleApplicationEventMulticaster. Diese Klasse unterstützt den asynchronen Aufruf des Listeners.

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

Die obige invokeListener-Methode ruft intern den Listener auf. Wie aus dem Code ersichtlich ist, wird der Listener asynchron aufgerufen, wenn der aktuelle Executor nicht leer ist. Wenn also Asynchronität erforderlich ist, machen Sie den Executor einfach nicht leer, sondern standardmäßig Der Executor ist leer. Zu diesem Zeitpunkt müssen wir einen Wert dafür festlegen. Als nächstes müssen wir sehen, wie der Broadcaster im Container erstellt wird und wo wir eingreifen müssen.

AnnotationConfigServletWebServerApplicationContext -》 ServletWebServerApplicationContext -》 GenericWebApplicationContext -》 GenericApplicationContext -》 AbstractApplicationContext -》 ConfigurableApplicationContext -》 ApplicationContext -》 ApplicationEventPublisher

Normalerweise erbt der von uns verwendete Container vom Typ AbstractApplicationContext. Wenn der Container startet, wird AbstractApplicationContext#initApplicationEventMulticaster aufgerufen, um den Broadcaster zu initialisieren:

	private ApplicationEventMulticaster applicationEventMulticaster;
	public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";

	protected void initApplicationEventMulticaster() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { // 判断容器中是否有一个 applicationEventMulticaster bean,有的话直接拿到使用
			this.applicationEventMulticaster =
					beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
			if (logger.isTraceEnabled()) {
				logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
			}
		}
		else {
			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() + "]");
			}
		}
	}

Stellen Sie fest, ob im Spring-Container eine Bean mit dem Namen applicationEventMulticaster vorhanden ist. Wenn ja, verwenden Sie sie als Event-Broadcaster. Andernfalls erstellen Sie einen SimpleApplicationEventMulticaster als Broadcaster und registrieren Sie ihn im Spring-Container.

Passen Sie einfach eine Bean vom Typ SimpleApplicationEventMulticaster an und nennen Sie applicationEventMulticaster. Legen Sie übrigens einen Wert für den Executor fest, um die asynchrone Ausführung des Listeners zu implementieren.

Die Implementierung ist wie folgt:

@Configuration
public class SyncListenerConfig {
    @Bean
    public ApplicationEventMulticaster applicationEventMulticaster() {
        // 创建一个事件广播器
        SimpleApplicationEventMulticaster result = new SimpleApplicationEventMulticaster();
        // 给广播器提供一个线程池,通过这个线程池来调用事件监听器
        ThreadPoolTool threadPoolTool = new ThreadPoolTool();
        ThreadPoolExecutor executor = threadPoolTool.build();
        // 设置异步执行器
        result.setTaskExecutor(executor);
        return result;
    }
}

@Slf4j
//@Data
public class ThreadPoolTool {
    private static int corePoolSize = Runtime.getRuntime().availableProcessors();
    private static int maximumPoolSize = corePoolSize * 2;
    private static long keepAliveTime = 10;
    private static TimeUnit unit = TimeUnit.SECONDS;
    private static BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(3);
    private static ThreadFactory threadFactory = new NameTreadFactory();
    private static RejectedExecutionHandler handler = new MyIgnorePolicy();

    private ThreadPoolExecutor executor;

    public ThreadPoolExecutor build() {
       executor  = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit,
                workQueue, threadFactory, handler);
        executor.prestartAllCoreThreads(); // 预启动所有核心线程
        return executor;
    }
}

@Slf4j
public class NameTreadFactory implements ThreadFactory {
    private AtomicInteger mThreadNum = new AtomicInteger(1);
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r, "my-thread-" + mThreadNum.getAndIncrement());
        log.info(thread.getName() + " has been created");
        return thread;
    }
}

Ausgabe nach dem Ausführen:

INFO []2023-02-15 14:58:49.182[org.im.eventtest.spring.UserRegisterService][31][http-nio-12000-exec-1][INFO]-用户name2注册成功
INFO []2023-02-15 14:58:49.184[org.im.eventtest.spring.SendEmailListener][24][my-thread-16][INFO]-给用户name2发送注册成功邮件!
INFO []2023-02-15 14:58:49.278[org.im.eventtest.spring.SendEmailListener][30][my-thread-15][INFO]-EventListener 给用户name2发送注册邮件成功!

5. Nutzungsvorschläge

  • Sie können den Spring-Event-Mechanismus verwenden, um Parameter zu übergeben, zu entkoppeln usw.;

  • Für einige Nicht-Hauptgeschäfte (die Hauptgeschäftsverarbeitung wird nach einem Ausfall nicht beeinträchtigt) kann der asynchrone Ereignismodus verwendet werden.

  • Ereignisse im Frühling können über Schnittstellen oder Annotationen erfolgen (am besten eine Methode einheitlich im Team nutzen).

Supongo que te gusta

Origin blog.csdn.net/Jernnifer_mao/article/details/133296192
Recomendado
Clasificación