首页 > 基础资料 博客日记

Spring事件(Application Event)使用和源码

2024-09-07 21:00:07基础资料围观127

Java资料网推荐Spring事件(Application Event)使用和源码这篇文章给大家,欢迎收藏Java资料网享受知识的乐趣

1、什么是Spring事件

Spring 框架中的事件处理机制是一种在Spring容器管理的bean之间异步传递消息的方式。Spring事件机制允许一个bean在发生某些事情时发布事件,而其他bean可以监听这些事件并做出反应。

Spring事件机制是观察者模式的一种实现,是除了事件源和监听者两个角色之外,还有一个 EventMultiCaster 的角色负责把事件转发(广播)给监听者。

2、事件的使用场景及优缺点

使用场景

  1. 用户认证通知
    • 当用户登录系统时,可以发布一个认证成功的事件,其他组件可以监听此事件并执行相关操作,如更新用户状态、记录登录日志等。
  2. 应用程序启动和关闭
    • 使用Spring的ContextRefreshedEventContextClosedEvent来监听应用程序的启动和关闭事件。
  3. 消息队列处理
    • 在消息消费过程中,可以发布自定义事件,当消息被成功处理后,触发后续操作。
  4. 异步处理
    • 对于不需要即时返回结果的长时间运行任务,可以发布事件让其他服务异步处理。
  5. 缓存更新
    • 当数据更新时,发布事件通知缓存服务更新缓存条目。
  6. 工作流状态变化
    • 在复杂的业务流程中,当某个步骤完成时,发布事件通知其他步骤或服务进行相应的处理。
  7. 资源变更通知
    • 如数据源变更、配置文件更改等,通过事件通知系统中的相关组件重新加载配置。
  8. 定时任务调度
    • 在定时任务执行前后发布事件,允许其他组件在任务执行前后进行干预或处理。
  9. 事务管理
    • 监听事务的开始和结束事件,进行事务相关的自定义处理。
  10. 错误和异常处理
  • 当系统中发生错误或异常时,发布事件以便集中处理或记录错误信息。

优点

  1. 解耦
    • 事件发布者和监听者之间是解耦的,它们不需要知道对方的存在,只需对事件本身有共同的理解。
  2. 扩展性
    • 新的监听器可以很容易地添加到系统中,而不需要修改现有的代码。
  3. 异步处理能力
    • 支持异步事件处理,有助于提高应用程序的响应性和吞吐量。
  4. 灵活性
    • 可以根据需要定义和使用自定义事件,传递特定的数据。
  5. 简化组件间的通信
    • 减少了组件之间的直接调用,简化了组件间的通信逻辑。
  6. 重用性
    • 事件监听器可以在不同的上下文中重用,提高了代码的重用性。
  7. 易于集成
    • 事件机制可以很容易地集成到现有的Spring应用程序中。

缺点

  1. 性能开销
    • 事件的发布和监听可能会引入一些性能开销,尤其是在事件处理逻辑复杂或监听器数量较多的情况下。
  2. 调试困难
    • 由于事件的传递是异步的,可能会使得问题的调试变得更加困难。
  3. 事件顺序问题
    • 在某些情况下,事件处理的顺序可能不容易控制,可能会导致不可预期的结果。
  4. 过度使用
    • 如果过度使用事件机制,可能会导致系统结构变得复杂和难以理解。
  5. 安全风险
    • 如果事件携带敏感数据,不恰当的处理可能会带来安全风险。
  6. 依赖管理
    • 在某些情况下,事件监听器的依赖管理可能比直接的bean依赖更加复杂。
  7. 资源消耗
    • 长时间运行的事件监听器可能会占用系统资源,尤其是在没有正确管理的情况下。
  8. 测试挑战
    • 测试事件驱动的系统可能比测试基于直接调用的系统更加困难。

3、使用

  1. 定义事件: 创建一个事件类,继承自ApplicationEvent
/**
* ApplicationEvent 的子类会被 ApplicationListener 监听并响应。
*/
public class CustomEvent extends ApplicationEvent {
    private String message;

    public CustomEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}
  1. 创建事件发布者: 在需要发布事件的组件中,注入ApplicationEventPublisher
@Component
public class EventPublisherBean {
    @Autowired
    private ApplicationEventPublisher publisher;
    

//    @Autowired 
//    ApplicationContext applicationContext;

    public void publishEvent(String message) {
        CustomEvent event = new CustomEvent(this, message);
        publisher.publishEvent(event);
        
        //也可以使用ApplicationContext发布事件,但通常不推荐
        //applicationContext.publishEvent(event);
    }
}
  1. 编写事件监听器: 创建一个事件监听器类,实现ApplicationListener接口或使用@EventListener注解。
@Component
public class CustomEventListener implements ApplicationListener<CustomEvent> {
    @Override
    public void onApplicationEvent(CustomEvent event) {
        String message = event.getMessage();
        // 处理事件
        System.out.println("Received event with message: " + message);
    }
}

或者使用@EventListener注解:

@Component
public class AnotherEventListener {
    @EventListener
    public void handleCustomEvent(CustomEvent event) {
        // 处理事件
        System.out.println("Handling event with message: " + event.getMessage());
    }
}
  1. 配置异步事件监听(可选): 如果需要异步处理事件,可以使用@Async注解。
    (测试时,因为懒方法上使用了@PostConstruct,结果发现执行不到当前@Async异步)
@Component
public class AsyncEventListener {
    @Async
    @EventListener
    public void handleCustomEvent(CustomEvent event) {
        // 异步处理事件
        System.out.println("Asynchronously handling event with message: " + event.getMessage());
    }
}
  1. 发布事件: 在适当的时机,使用注入的ApplicationEventPublisher发布事件。
// 在某个业务逻辑中
eventPublisherBean.publishEvent("Hello, World!");
  1. 启动Spring应用程序: 确保Spring应用程序上下文被正确初始化,以便注册事件发布者和监听器。
public static void main(String[] args) {
    ApplicationContext context = SpringApplication.run(YourSpringBootApplication.class, args);

    // 从容器中获取Bean并触发事件
    EventPublisherBean eventPublisherBean = context.getBean(EventPublisherBean.class);
    eventPublisherBean.publishEvent("Event from main method");
}

4、源码解读

执行顺序
主要方法
AbstractApplicationContext#publishEvent(org.springframework.context.ApplicationEvent)

/**
 * 发布给定的事件到所有监听器。
 * 如果事件不是{@link ApplicationEvent}的实例,它将被封装在一个{@link PayloadApplicationEvent}中。
 * 如果提供了事件类型,则用于多播事件;否则,将从封装的事件中解析事件类型。
 * 如果当前上下文尚未初始化多播器,则事件将被添加到早期事件列表中,待上下文初始化后进行多播。
 * 如果当前上下文有父上下文,事件也将被发布到父上下文。
 * 
 * @param event 要发布的事件,可以是任何对象,如果事件不是{@link ApplicationEvent}的实例,它将被封装。
 * @param eventType 事件类型,可为null。如果提供,将用于多播事件;如果事件被封装,则从中解析事件类型。
 * @since 4.2
 */
AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)

/**
 * 多播给定的事件到所有的监听器。
 * 如果提供了事件类型,则使用提供的事件类型;否则,尝试解析默认的事件类型。
 * 如果存在任务执行器,则使用它来异步执行监听器调用;否则,直接同步调用监听器。
 *
 * @param event 需要多播的事件对象。
 * @param eventType 可选的事件类型,用于指定特定类型的事件监听器。
 */
SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)

/**
 * 根据给定的事件和事件类型,获取匹配的应用程序监听器集合。
 * 通过提前排除不匹配的监听器来优化性能。
 * 
 * @param event 发生的事件,用于匹配监听器。允许基于缓存的匹配信息提前排除非匹配的监听器。
 * @param eventType 事件的类型,用于精确匹配监听器。
 * @return 匹配的监听器集合。
 * @see org.springframework.context.ApplicationListener
 */
AbstractApplicationEventMulticaster#getApplicationListeners(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)

/**
 * 根据给定的事件类型和源类型,实际检索应用监听器。
 * <p>
 * 此方法旨在高效地检索适用于特定事件和源的监听器集合。它首先从默认检索器中获取所有预注册的监听器,
 * 然后根据事件类型和源类型过滤它们。此外,它还处理通过bean名称注册的监听器,这可能与直接注册的监听器重叠,
 * 但可能包含额外的元数据。
 * 
 * @param eventType 事件类型
 * @param sourceType 事件源类型
 * @param retriever 用于缓存目的的监听器检索器,如果需要填充
 * @return 给定事件和源类型的预过滤应用监听器列表
 */
AbstractApplicationEventMulticaster#retrieveApplicationListeners(ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever)

/**
 * 判断给定的监听器是否支持给定的事件。
 * <p>默认实现检测{@link SmartApplicationListener}和{@link GenericApplicationListener}接口。
 * 对于标准的{@link ApplicationListener},会使用{@link GenericApplicationListenerAdapter}来检查目标监听器的泛型声明类型。
 * @param listener 待检查的目标监听器
 * @param eventType 待检查的事件类型
 * @param sourceType 待检查的事件源类型
 * @return 如果给定的监听器应包含在给定事件类型的候选监听器中,则返回true
 */
AbstractApplicationEventMulticaster#supportsEvent(org.springframework.context.ApplicationListener<?>, org.springframework.core.ResolvableType, java.lang.Class<?>)

/**
 * 通过检查其泛型声明的事件类型,提前过滤豆定义的监听器。
 * <p>
 * 如果这个方法对给定的监听器作为初步检查返回 {@code true},则之后会通过
 * {@link #supportsEvent(ApplicationListener, ResolvableType, Class)} 方法来获取和完全评估监听器实例。
 * @param beanFactory 包含监听器bean的BeanFactory
 * @param listenerBeanName BeanFactory中监听器bean的名字
 * @param eventType 需要检查的事件类型
 * @return 对于给定的事件类型,是否应该将给定的监听器包含在候选列表中
 * @see #supportsEvent(Class, ResolvableType)
 * @see #supportsEvent(ApplicationListener, ResolvableType, Class)
 */
AbstractApplicationEventMulticaster#supportsEvent(org.springframework.beans.factory.config.ConfigurableBeanFactory, java.lang.String, org.springframework.core.ResolvableType)

/**
 * 调用给定的ApplicationListener处理给定的ApplicationEvent。
 * <p>
 * 此方法封装了调用监听器的实际逻辑,以便在存在错误处理程序时能够处理任何异常。
 * 如果存在错误处理程序,任何在调用监听器过程中抛出的异常都会被错误处理程序处理。
 * 如果不存在错误处理程序,则直接调用监听器,不进行异常处理。
 * 
 * @param listener 要调用的应用程序事件监听器。
 * @param event 要传递给监听器的应用程序事件。
 * @since 4.1
 */
SimpleApplicationEventMulticaster#invokeListener(ApplicationListener<?> listener, ApplicationEvent event)

/**
 * 调用对应的自定义监听器
 * 调用应用程序监听器的onApplicationEvent方法。
 * 
 * 此方法处理了调用监听器事件时可能发生的ClassCastException。如果发生此类异常,
 * 且异常信息与事件类型不匹配,那么将记录一个调试消息,并抑制异常。
 * 这种情况可能发生在使用lambda定义的监听器上,无法解析其泛型事件类型。
 * 
 * @param listener 要调用的应用程序监听器。
 * @param event 要传递给监听器的应用程序事件。
 * @throws ClassCastException 如果监听器的事件类型与事件不匹配,且异常信息不符合特定模式,则抛出此异常。
 */
SimpleApplicationEventMulticaster#doInvokeListener(ApplicationListener listener, ApplicationEvent event)
ApplicationEvent

package org.springframework.context;

import java.util.EventObject;

/**
 * 类将由所有应用程序事件扩展。抽象,因为直接发布通用事件没有意义。
 * 抽象类 作为所有具体应用事件的基类。
 * ApplicationEvent 的子类会被 ApplicationListener 监听并响应。
 * ApplicationContext 提供了一个事件发布机制,允许任何实现了 ApplicationListener 接口的组件订阅并处理这些事件。
 * 具体的事件类型通常由 ApplicationEvent 的子类来定义,例如 ContextRefreshedEvent 或 ContextStartedEvent,它们表示应用上下文被刷新或启动等特定的事件。
 * 
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
public abstract class ApplicationEvent extends EventObject {

    /** 使用Spring 1.2中的serialVersionUID实现互操作性 */
    private static final long serialVersionUID = 7099057708183571937L;

    /** 事件发生时的系统时间。 */
    private final long timestamp;


    /**
	 * 创建新的ApplicationEvent。
	 * @param source the object on which the event initially occurred (never {@code null})
	 */
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }


    /**
	 * 返回事件发生时的系统时间(毫秒)。
	 */
    public final long getTimestamp() {
        return this.timestamp;
    }

}

AbstractApplicationContext
#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)
	/**
	 * 发布给定的事件到所有监听器。
	 * 如果事件不是{@link ApplicationEvent}的实例,它将被封装在一个{@link PayloadApplicationEvent}中。
	 * 如果提供了事件类型,则用于多播事件;否则,将从封装的事件中解析事件类型。
	 * 如果当前上下文尚未初始化多播器,则事件将被添加到早期事件列表中,待上下文初始化后进行多播。
	 * 如果当前上下文有父上下文,事件也将被发布到父上下文。
	 * 
	 * @param event 要发布的事件,可以是任何对象,如果事件不是{@link ApplicationEvent}的实例,它将被封装。
	 * @param eventType 事件类型,可为null。如果提供,将用于多播事件;如果事件被封装,则从中解析事件类型。
	 * @since 4.2
	 */
	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);
			}
		}
	}
SimpleApplicationEventMulticaster
#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
/** org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType) */
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
    Executor executor = this.getTaskExecutor();			//事件处理的线程池(没有设置异步处理的线程池则由发布的线程处理)
    Iterator var5 = this.getApplicationListeners(event, type).iterator();	//根据事件类型寻找已经注册的感兴趣的监听器

    while(var5.hasNext()) {	
        ApplicationListener<?> listener = (ApplicationListener)var5.next();
        if (executor != null) {
            executor.execute(() -> {		//交由监听器处理事件(异步)
                this.invokeListener(listener, event);
            });
        } else {
            this.invokeListener(listener, event);
        }
    }

}

#doInvokeListener
/** org.springframework.context.event.SimpleApplicationEventMulticaster#doInvokeListener */
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
        listener.onApplicationEvent(event);		//监听器继承ApplicationListener重写的处理方法
    } catch (ClassCastException var6) {
        String msg = var6.getMessage();
        if (msg != null && !this.matchesClassCastMessage(msg, event.getClass())) {
            throw var6;
        }

        Log logger = LogFactory.getLog(this.getClass());
        if (logger.isTraceEnabled()) {
            logger.trace("Non-matching event type for listener: " + listener, var6);
        }
    }

}


文章来源:https://blog.csdn.net/qq_41999771/article/details/140710686
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云