Un breve análisis del mecanismo de eventos Spring Boot

1. Información general

Entre los patrones de diseño, el patrón de observador es un patrón de diseño de uso común. Wikipedia lo explica de la siguiente manera:

 El patrón de observador es un tipo de patrón de diseño de software. En este patrón, un objeto objetivo gestiona todos los objetos observadores que dependen de él y emite notificaciones activamente cuando cambia su propio estado. Esto generalmente se logra llamando a los métodos proporcionados por cada observador.

En nuestro desarrollo empresarial diario, el patrón de observador juega un papel importante para nosotros a la hora de realizar el desacoplamiento empresarial, la transferencia de parámetros, etc. Tome el escenario de registro de usuario como ejemplo. Supongamos que cuando se completa el registro de usuario, es necesario enviar correos electrónicos, cupones, etc. al usuario, como se muestra en la siguiente figura:

imagen

imagen

  • Una vez que UserService completa su propia lógica de registro de usuario, solo necesita publicar un evento UserRegisterEvent sin prestar atención a otra lógica de expansión.

  • Otros servicios pueden suscribirse al evento UserRegisterEvent para implementar una lógica de expansión personalizada.

Nota: El patrón de publicación-suscripción pertenece al patrón de observador en un sentido amplio.

En el patrón de observador, el observador necesita suscribirse directamente al evento de destino; después de que el objetivo envía un evento cuyo contenido cambia, recibe el evento directamente y responde.

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

En el modelo de publicación-suscripción, existe un canal de publicación adicional entre el editor y el suscriptor; por un lado, los eventos se reciben del editor y, por otro lado, los eventos se publican para los suscriptores; el suscriptor necesita suscribirse al evento desde el canal del evento para evitar al editor Crear una relación de dependencia con los suscriptores

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

En pocas palabras, el modo de publicación-suscripción pertenece al modo de observador en un sentido amplio. Basado en el Sujeto y el Observador del modo de observador, se introduce el Canal de eventos como intermediario para desacoplarlo aún más.

2. Conceptos en patrón de eventos

  • Fuente del evento : el desencadenante del evento, como registrar la información del usuario, almacenarla en la base de datos y publicar "El usuario XX se ha registrado correctamente".

  • Evento : un objeto que describe lo que sucedió, por ejemplo: XX evento de registro exitoso

  • Oyente de eventos : cuando ocurre el evento, realizará algún procesamiento, como enviar correos electrónicos después de un registro exitoso, regalar puntos, emitir cupones...

3. Pasos de uso del evento de primavera

  • Definir evento

    Los eventos personalizados deben heredar la clase ApplicationEvent para implementar eventos personalizados. Además, el origen del evento se puede obtener a través de sus  source propiedades y timestamp el tiempo de ocurrencia se puede obtener a través de las propiedades.

  • definir oyente

    Los detectores de eventos personalizados deben implementar la interfaz ApplicationListener y el método onApplicationEvent para manejar eventos de interés.

  • Crear locutor de eventos

    Cree un transmisor de eventos para implementar la interfaz ApplicationEventMulticaster, o puede usar SimpleApplicationEventMulticaster definido por Spring:

    ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();
  • Registrar un oyente de eventos con la emisora

    Registre el detector de eventos en la emisora ​​ApplicationEventMulticaster,

    applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreaterListener());
  • Publicar eventos a través de la emisora

    Para transmitir un evento, llame al método ApplicationEventMulticaster#multicastEvent para transmitir el evento. En este momento, el oyente de la emisora ​​que está interesado en este evento manejará el evento.

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

4. Cómo utilizar

4.1 Enfoque orientado a la interfaz

Caso: implemente la función de publicar eventos después del registro exitoso del usuario y luego enviar correos electrónicos en el oyente.

Evento de registro de usuario:

Cree la clase de evento UserRegisterEvent, herede la clase ApplicationEvent y los eventos de registro de usuarios. El código se muestra a continuación:

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

    public String getUserName() {
        return userName;
    }
}

Enviar escucha de correo:

Cree la clase SendEmailListener, servicio de buzón. El código se muestra a continuación:

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

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

Aviso:

  • Implemente la interfaz ApplicationListener y  E establezca eventos de interés a través de genéricos, como UserRegistryEvent;

  • Implemente  #onApplicationEvent(E event) un método para realizar un procesamiento personalizado para el evento UserRegisterEvent monitoreado.

Servicio de registro de usuario: función de registro + publicación de evento de registro de usuario

Cree la clase UserRegisterService, servicio de usuario. El código se muestra a continuación:

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

}

Aviso:

  • La interfaz ApplicationEventPublisherAware se implementa arriba: el contenedor Spring inyectará ApplicationEventPublisher a través de setApplicationEventPublisher, y luego podemos usarlo para publicar eventos;

  • Después de ejecutar la lógica de registro, llame al #publishEvent(ApplicationEvent event)método [] de ApplicationEventPublisher para publicar el evento [UserRegisterEvent]

transferir:

@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";
    }

}

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

Producción:

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

Principio:
durante el proceso de creación de un bean, el contenedor Spring determinará si el bean es del tipo ApplicationListener y luego lo registrará como oyente en 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);
			}
		}
	}

El código fuente de esta pieza se encuentra en el método siguiente,

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 Métodos para la anotación @EventListener

Puede especificar una expresión SpEL a través del atributo de condición. Si se devuelve cualquiera de "verdadero", "activado", "sí" o "1", el evento se procesará; de lo contrario, no.

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

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

Producción:

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

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

Producción:

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

principio:

EventListenerMethodProcessor implementa la interfaz SmartInitializingSingleton. El contenedor Spring llamará al método afterSingletonsInstantiated en la interfaz SmartInitializingSingleton después de que se creen todos los beans singleton. El código fuente para manejar las anotaciones @EventListener en primavera se encuentra en el siguiente método

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 Clasificación de oyentes

Si hay varios oyentes para un evento, el orden de ejecución de los oyentes está desordenado de forma predeterminada, pero podemos especificar el orden de los oyentes.

4.3.1 Implementar el oyente a través de la interfaz:

Tres formas de especificar el orden de los oyentes:

  • Implemente la interfaz org.springframework.core.Ordered #getOrder. Cuanto menor sea el valor de retorno, mayor será el orden.

  • Implementar la interfaz org.springframework.core.PriorityOrdered #getOrder

  • Utilice la anotación org.springframework.core.annotation.Order en la clase

4.3.2 A través de @EventListener:

Puede utilizar la anotación @Order (valor de pedido) en el método marcado como @EventListener para marcar el pedido.

4.4 Modo asincrónico del oyente

En última instancia, se llama al oyente a través de la implementación interna de ApplicationEventMulticaster. La clase de implementación predeterminada es SimpleApplicationEventMulticaster. Esta clase admite llamadas asincrónicas al oyente.

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

El método invokeListener anterior llama internamente al oyente. Como se puede ver en el código, si el ejecutor actual no está vacío, el oyente se llamará de forma asíncrona, por lo que si es necesario asíncrono, simplemente haga que el ejecutor no esté vacío, pero de forma predeterminada El ejecutor está vacío. En este momento, debemos establecer un valor para él. A continuación, debemos ver cómo se crea el transmisor en el contenedor y dónde debemos intervenir.

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

Por lo general, el contenedor que utilizamos hereda del tipo AbstractApplicationContext. Cuando se inicia el contenedor, se llamará a AbstractApplicationContext#initApplicationEventMulticaster para inicializar la emisora:

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

Determine si hay un bean llamado applicationEventMulticaster en el contenedor Spring. Si es así, utilícelo como transmisor de eventos. De lo contrario, cree un SimpleApplicationEventMulticaster como transmisor y regístrelo en el contenedor Spring.

Simplemente personalice un bean de tipo SimpleApplicationEventMulticaster y nombre applicationEventMulticaster. Por cierto, establezca un valor para el ejecutor para implementar la ejecución asincrónica del oyente.

La implementación es la siguiente:

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

Salida después de ejecutar:

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. Sugerencias de uso

  • Puede utilizar el mecanismo de eventos de primavera para pasar parámetros, desacoplar, etc.;

  • Para algunas empresas no principales (el procesamiento de la empresa principal no se verá afectado después de una falla), se puede utilizar el modo de evento asincrónico;

  • Los eventos en primavera se pueden realizar mediante interfaces o anotaciones (es mejor utilizar un método de manera uniforme dentro del equipo).

おすすめ

転載: blog.csdn.net/Jernnifer_mao/article/details/133296192