技术派Spring事件监听机制及原理

Spring事件监听机制是Spring框架中的一种重要技术,允许组件之间进行松耦合通信。通过使用事件监听机制,应用程序的各个组件可以在其他组件不直接引用的情况下,相互发送和接受消息。

需求

在技术派中有这样一个需求,当发布文章或者文章下线时,会发布一个事件给SiteMap(站点地图,帮助搜索引擎有效的抓取和索引网站),SiteMap监听到该事件后会进行更新。

事件监听的本质是观察者模式的应用包括事件、事件监听器、事件发布器等主要组件。

事件:一个实现了ApplicationEvent类的对象,代表了应用程序中某个特定的事件。我们可以根据需要创建自定义事件,只要继承ApplicationEvent类并添相关的属性和方法就可以了。

事件监听器:实现了ApplicationListener<E>接口 的对象,其中E表示事件监听器需要处理的事件类型。监听器可以通过onApplicationEvent(E event)方法处理接受到的事件 ,另外也可以使用**@EventListener注解来简化事件监听器的实现,技术派正是采用的这种方式。**

事件发布器:事件发布器负责将事件发布给所有关注该事件的监听器 ,在Spring中,ApplicationEventPublisher接口定义了事件发布的基本功能,而ApplicationEventPublisherAware接口允许组件获取到事件发布器的引用。Spring的核心容器ApplicationContext实现了ApplicationEventPublisher接口,因此在Spring应用中,通常直接使用ApplicationContext作为事件发布器,技术派采用该方式。

实例

第一步(事件)

创建自定义事件ArticleMsgEvent,继承ApplicationEvent。

java 复制代码
@Getter
@Setter
@ToString
@EqualsAndHashCode(callSuper = true)
public class ArticleMsgEvent<T> extends ApplicationEvent {

    private ArticleEventEnum type;

    private T content;


    public ArticleMsgEvent(Object source, ArticleEventEnum type, T content) {
        super(source);
        this.type = type;
        this.content = content;
    }
}

类上的四个注解为lombok提供。两个字段,

type:枚举类型(ArticleEventEnum),代表事件的类型。

表示文章上线或者下线。

content:泛型(T),表示事件的内容,在本例中,我们会传一个文章的ID。

source:在构造方法里卖我们还会传一个Object类型的数据,表示事件的来源,也就是事件的发布者。

ApplicationEvent:是Spring Framework框架中用于定义事件的基类。

第二步(发布事件)

定义SpringUtil工具类,实现了ApplicationContexAware

java 复制代码
@Component
public class SpringUtil implements ApplicationContextAware {


    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtil.context = applicationContext;
    }


    /**
     * 发布事件消息
     *
     * @param event
     */
    public static void publishEvent(ApplicationEvent event) {
        context.publishEvent(event);
    }

通过实现ApplicationContextAware接口,可以让这个类在Spring容器启动时自动获得ApplicationContext引用。(作为事件发布器)@Component可以让该类被Spring容器自动实例化和管理。

自动装配过程是通过Spring得ApplicationContextAwareProcessor类实现得,它是一个后置处理器。在Spring容器初始化时,他会检查所有得Bean,如果Bean实现了ApplicationContextAware接口。他会调用setApplicationContext方法将ApplicationContext的引用传递给Bean。

第三步(用事件)

通过调用SpringUtil.publishEvent()发布事件。在ArticleSettingServiceImpl类中。

java 复制代码
@Override
public void updateArticle(ArticlePostReq req) {
ArticleDO article = articleDao.getById(req.getArticleId());
if (article == null) {
    return;
}

if (StringUtils.isNotBlank(req.getTitle())) {
    article.setTitle(req.getTitle());
}
article.setShortTitle(req.getShortTitle());

ArticleEventEnum operateEvent = null;
if (req.getStatus() != null) {
    article.setStatus(req.getStatus());
    if (req.getStatus() == PushStatusEnum.OFFLINE.getCode()) {
        operateEvent = ArticleEventEnum.OFFLINE;
    } else if (req.getStatus() == PushStatusEnum.REVIEW.getCode()) {
        operateEvent = ArticleEventEnum.REVIEW;
    } else if (req.getStatus() == PushStatusEnum.ONLINE.getCode()) {
        operateEvent = ArticleEventEnum.ONLINE;
    }

    //            switch (req.getStatus()){
    //                case 0 :
    //                    operateEvent = ArticleEventEnum.OFFLINE;
    //                    break;
    //                case 3 :
    //                    operateEvent = ArticleEventEnum.REVIEW;
    //                    break;
    //                case 2 :
    //                    operateEvent = ArticleEventEnum.ONLINE;
    //                    break;
    //                default:
    //                    break;
    //            }

}
articleDao.updateById(article);

if (operateEvent != null) {
    // 发布文章待审核、上线、下线事件
    SpringUtil.publishEvent(new ArticleMsgEvent<>(this, operateEvent, article.getId()));
}
}

第四步(监听并处理事件)

通过 @EventListener注解来处理事件,在SitemapServiceImpl类中可以看到。

java 复制代码
/**
     * 基于文章的上下线,自动更新站点地图
     *
     * @param event
     */
@EventListener(ArticleMsgEvent.class)
public void autoUpdateSiteMap(ArticleMsgEvent<Long> event) {
    ArticleEventEnum type = event.getType();
    if (type == ArticleEventEnum.ONLINE) {
        addArticle(event.getContent());
    } else if (type == ArticleEventEnum.OFFLINE || type == ArticleEventEnum.DELETE) {
        rmArticle(event.getContent());
    }
}



public void addArticle(Long articleId) {
RedisClient.hSet(SITE_MAP_CACHE_KEY, String.valueOf(articleId), System.currentTimeMillis());
}


public void rmArticle(Long articleId) {
    RedisClient.hDel(SITE_MAP_CACHE_KEY, String.valueOf(articleId));
}

当ArticleMsgEvent类型的事件被发布时,此方法自动被触发,在该方法中,首先获得事件的类型(ArticleEventEnum枚举值),然后根据事件类型执行相应的操作,上线时将文章添加到SiteMap,下线时从SiteMap中删除。

测试

这个就时技术派中的事件监听机制了。

启动Redis,启动服务端,启动admin端,在后端找一篇文章下线文章。

就可以在debug模式下看到事件触发了。

原理分析

Spring事件监听机制涉及到s四个主要的类:

事件对象:ApplicationEvent

事件监听器:ApplicationLisener,事件监听器,可以通过@EventListener注解定义事件处理方法,而无需实现ApplicationListener接口

事件发布者:ApplicationEventPublisher,在Spring中可以通过ApplicationEventPublisherAware接口或使用@Autowired注解来注入ApplicationEventPublisher实例,当事件被发布时,Spring会自动调用已注册的ApplicationListener实现类得onApplicationEvent()方法。

事件管理者:ApplicationEventMulticaster,管理监听器和发布事件,通常由SimpleApplicationEventMulticaster类实现。他会遍历所有已经注册的监听器,并调用他们的onApplicationEvent()方法。

ApplicationEvent

ApplicationEvent继承了EventObject对象。

来看看ApplicationEvent的子类关系图

ApplicationEvent 有一个重要的子类 ApplicationContextEvent,而ApplicationContextEvent 又有 4 个重要的子类:

ContextStartedEvent:当 Spring 容器启动时触发该事件。这意味着所有 Bean 都已加载,并且 ApplicationContext 已初始化。

ContextStoppedEvent:当 Spring 容器停止时触发该事件。当容器关闭并停止处理请求时,通常会触发此事件。

ContextRefreshedEvent:当 ApplicationContext 刷新时触发该事件。这表示所有Bean 都已创建,并且已初始化所有单例 Bean(前提是它们在容器初始化时需要初始化)

**ContextClosedEvent:**当 Spring 容器关闭时触发该事件。这表示所有 Bean 都已销塾Spring 容器已清理资源并停止,

ApplicationListener

ApplicationListener继承EventListener接口,并要求实现onApplicationEvent(E event)方法。

java 复制代码
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E event);
}

onApplicationEvent(E event)方法:当发布某个事件时,所有注册的ApplicationListener 实例的 onApplicationEvent 方法都会被调用。在这个方法中,可以编写处理特定事件的逻辑。此方法接收一个类型为E的参数,这是 ApplicationEvent 的子类表示触发的事件。

当 Spring 应用启动时,Spring 会扫描所有的 Bean,寻找使用了 @EventListener 注解的方法。一旦找到这样的方法,Spring 会为这些方法创建 ApplicationListener 实例并将其注册到 ApplicationEventMulticaster。

ApplicationEventMulticaster

ApplicationEventMulticaster 是一个接口,负责管理监听器和发布事件,包含了注册监听器、移除监听器以及发布事件的方法。

Spring 容器中通常会有一个默认的实现,如 SimpleApplicationEventMulticaster,继承了AbstractApplicationEventMulticaster.

AbstractApplicationEventMulticaster 主要实现了管理监听器的方法(上面接口的前 5 个方法),比如说 addApplicationListener。

java 复制代码
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);
}

最核心的一句代码: this.defaultRetriever.applicationListeners.add(listener);,其内部类 DefaultListenerRetriever 里面有两个集合,用来记录维护事件监听器

这就和设计模式中的发布订阅模式一样了,维护一个 List,用来管理所有的订阅者,当发布者发布消息时,遍历对应的订阅者列表,执行各自的回调 handler。

再来看 SimpleApplicationEventMulticaster 类实现的广播事件逻辑

multicastEvent 的主要作用是将给定的 ApplicationEvent 广播给所有匹配的监听器

首先,通过检査 eventType 参数是否为 nul 来确定事件类型。如果 eventType 为null,则使用 resolveDefaultEventType(event)方法从事件对象本身解析事件类型

获取 Executor,它是一个可选的任务执行器,用于在异步执行监听器时调用。如果没有配置 Executor,则默认为 nul,表示使用同步执行。

使用 getApplicationListeners(event,type)方法获取所有匹配给定事件类型的监听器。

对于每个匹配的监听器,检查是否有 Executor 配置。如果存在 Executor,则使用executor.execute()方法将监听器的调用封装到一个异步任务中。如果没有配置Executor,则直接同步调用监听器。

使用 invokeListener(listener,event)方法调用监听器的 onApplicationEvent方法,将事件传递给监听器。

通过这个实现,SimpleApplicationEventMulticaster 可以将事件广播给所有关心该事件的监听器,同时支持同步和异步执行模式。

最后调用 istener.onApplicationEvent(event);也就是我们通过实现接口ApplicationListener 的方式来实现监听器的 onApplicationEvent 实现逻辑。

ApplicationEventPublisher

ApplicationEventPublisher 是一个接口,用于将事件发布给所有感兴趣的监听器。

这个接口的实现类通常会将事件委托给

ApplicationEventMulticaster。在 Spring 中ApplicationContext 通常充当事件发布者,它就实现了 ApplicationEventPublisher 接口。

ApplicationContext 的 publishEvent 方法的逻辑实现主要在类AbstractApplicationContext 中:

这段代码的主要逻辑在这:

这段代码的主要作用是在 ApplicationContext 初始化时处理应用程序事件的发布。当ApplicationContext 还没有完全初始化时,例如在refresh()方法中earlyApplicationEvents 列表会被用来保存早期的事件。在这个阶段ApplicationEventMulticaster 还没有完全配置好,因此无法直接发布事件。这些早期的事件将在 ApplicationContext 初始化完成后,ApplicationEventMulticaster 配置好后,通过 finishRefresh()方法中的 publishEvent(new ContextRefreshedEvent(this));发布。

当 ApplicationContext 初始化完成后,earlyApplicationEvents 列表将被设置为 null。此时,事件可以直接通过 getApplicationEventMulticaster().multicastEvent(applicationEvent,eventType)方法发布给所有匹配的监听器,

这个机制确保了在 ApplicationContext 初始化过程中产生的事件不会丢失,而是在ApplicationContext 初始化完成后被正确地发布给所有感兴趣的监听器。

总结

这篇内容通过源码的形式讲解了 Spring 事件监听机制及其原理。

相关推荐
丁总学Java38 分钟前
nohup java -jar supporterSys.jar --spring.profiles.active=prod &
java·spring·jar
曲邹萌1 小时前
springboot-vue excel上传导出
vue.js·spring boot·导入导出
小扳2 小时前
微服务篇-深入了解使用 RestTemplate 远程调用、Nacos 注册中心基本原理与使用、OpenFeign 的基本使用
java·运维·分布式·后端·spring·微服务·架构
HaiFan.2 小时前
Spring日志
java·spring boot
Hello Dam2 小时前
基于 Spring Boot 实现图片的服务器本地存储及前端回显
服务器·前端·spring boot
豪宇刘3 小时前
Spring MVC
java·spring·mvc
White graces3 小时前
Spring MVC练习(前后端分离开发实例)
java·开发语言·前端·后端·spring·java-ee·mvc
安安啦3 小时前
SSM--SpringMVC复习(二)
spring·mvc
小小李程序员12 小时前
LRU缓存
java·spring·缓存
cnsxjean12 小时前
SpringBoot集成Minio实现上传凭证、分片上传、秒传和断点续传
java·前端·spring boot·分布式·后端·中间件·架构