Spring 事件

1. 概述

在本教程,我们将讨论如何在Spring中使用事件

事件是Spring框架中较被忽视但非常有用的功能之一。正如Spring中许多其它的功能,事件发布是ApplicationContext提供的能力之一。

有一些简单的准则需要遵循:

  • Spring Framework 4.2 之前,事件类应该继承ApplicationEvent类。从4.2版本开始,事件类不再需要继承ApplicationEvent类。
  • 发布者应该注入ApplicationEventPublisher对象。
  • 监听者应该实现ApplicationListener接口。

2. 自定义事件

Spring允许我们创建和发布默认为同步的自定义事件。这有几个优点,例如监听器能够参与到发布者的事务上下文中。

2.1 一个简单的应用事件

让我们创建一个简单的事件类 --- 只需要一个简单的占位符来存储事件数据。

在这个例子中,事件类包含一个String message。

JAVA 复制代码
public class CustomSpringEvent extends ApplicationEvent { 
	private String message;
	public CustomSpringEvent(Object source, String message) {
		super(source);
		this.message = message; 
	} 
	public String getMessage() { 
		return message; 
	} 
}

2.2 一个发布者

现在让我们创建该事件的发布者。发布者构造事件对象并将其发布给任务正在监听的对象。

要发布事件,发布者可以简单地注入ApplicationEventPublishere并使用publishEvent()API;

JAVA 复制代码
@Component
public class CustomSpringEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void publishCustomEvent(final String message) {
        System.out.println("Publishing custom event. ");
        CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
        applicationEventPublisher.publishEvent(customSpringEvent);
    }
}

另外,发布者类可以实现ApplicationEventPulisherAware接口,这也会在应用启动时注入事件发布者。通常,仅仅通过@Autowired注入发布者就已经足够简单了。

从Spring Fameword 4.2开始,ApplicationEventPublisher接口为publishEvent(Object event)方法提供了一个新的重载,该方法接受任何对象作为事件。因此,Spring事件不再需要扩展ApplicationEvent类。

2.3 一个监听器

最后,让我们创建监听器。

监听器唯一的要求是作为一个bean并实现ApplicationListnener接口:

JAVA 复制代码
@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
    @Override
    public void onApplicationEvent(CustomSpringEvent event) {
        System.out.println("Received spring custom event - " + event.getMessage());
    }
}

注意我们自定义的监听器是如何使用自定义事件的泛型类型参数化的,这使得onApplicationEvent()方法类型安全。这也避免了必须检查对象是否为特定事件类的实例并将其强制转换。

而且,正如已经讨论过的(默认Spring事件是同步的),doStuffAndPublishAnEvent()方法会阻塞,直到所有监听器完成事件处理。

3. 创建异步事件

在某些情况下,发布同步事件并不是我们所期望的 --- 我们可能需要异步处理我们的事件

我们可以通过创建一个带有执行器的ApplicationEventMulticaster bean在配置中打开这个功能。

就我们目前的需求而言,SimpleAsyncTaskExecutor工作得很好:

JAVA 复制代码
@Configuration
public class AsynchronousSpringEventsConfig {
    @Bean(name = "applicationEventMulticaster")
    public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
        SimpleApplicationEventMulticaster eventMulticaster =
          new SimpleApplicationEventMulticaster();
        
        eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return eventMulticaster;
    }
}

事件、发布者和监听者的实现与之前相同,但现在监听者将在单独的线程中异步处理事件

4. 现有框架事件

Spring本身发布了多种开箱即用的事件。例如,ApplicationContext将触发各种框架事件:ContextRefreshedEventContextStartedEventRequestHandledEvent等。

这些事件为应用开发人员提供了一个选项,以便在应用和上下文的生命周期中插入他们自己的自定义逻辑。

这是一个快速实例,展示了一个监听器监听上下文刷新:

JAVA 复制代码
public class ContextRefreshedListener 
  implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent cse) {
        System.out.println("Handling context re-freshed event. ");
    }
}

要了解更多关于现有框架事件的信息,请查看我们的下一个教程

5. 注解驱动的事件监听器

从Spring 4.2开始,事件监听器不需要是实现了ApplicationListener接口的bean --- 它可以通过@EvenetListener注解注册在任何托管bean的公共方法上。

JAVA 复制代码
@Component
public class AnnotationDrivenEventListener {
    @EventListener
    public void handleContextStart(ContextStartedEvent cse) {
        System.out.println("Handling context started event.");
    }
}

与以前一样,方法签名声明了它消费的事件类型。

默认情况下,监听器是同步调用的。然而,我们可以通过添加@Async注解使其异步。我们只需记住在应用程序中启用异步支持。

6. 泛型支持

也可以在事件类型中派发带有泛型信息的事件。

6.1 一个泛型应用事件

让我们创建一个泛型事件类型

在我们的例子中,事件类包含任何内容和一个成功状态指示器。

JAVA 复制代码
public class GenericSpringEvent<T> {
    private T what;
    protected boolean success;

    public GenericSpringEvent(T what, boolean success) {
        this.what = what;
        this.success = success;
    }
    // ... standard getters
}

注意GenericSpringEventCustomSpringEvent之间的区别。我们现在有了发布任意事件的灵活性,并且不再需要从ApplicationEvent扩展。

6.2 一个监听器

现在让我们创建该事件的监听器

我们可以像之前一样通过实现ApplicationListener接口来定义监听器:

JAVA 复制代码
@Component
public class GenericSpringEventListener 
  implements ApplicationListener<GenericSpringEvent<String>> {
    @Override
    public void onApplicationEvent(@NonNull GenericSpringEvent<String> event) {
        System.out.println("Received spring generic event - " + event.getWhat());
    }
}

但不幸的是,这个定义要求我们从ApplicationEvent类继承GenericSpringEvent。所以对于这个教程,让我们使用之前讨论过的注解驱动的事件监听器

也可以通过在@EventListener注解上定义一个布尔Spel表达式来使事件监听器有条件。

在这种情况下,事件处理程序仅在成功的GenericSpringEvent<String>事件发生时被调用。

JAVA 复制代码
@Component
public class AnnotationDrivenEventListener {
    @EventListener(condition = "#event.success")
    public void handleSuccessful(GenericSpringEvent<String> event) {
        System.out.println("Handling generic event (conditional).");
    }
}

Spring表达式语言(SpEL)是一种强大的表达式语言,在另一个教程中有详细介绍。

6.3 一个发布者

事件发布者类似于上面描述的。但由于类型擦除,我们需要发布一个解析我们将要 过滤的泛型参数的事件,比如 class GenericStringSpringEvent extend GenericSpringEvent<String>

此外,发布事件还有另一种方式。如果我们从用@EventListener注解的方法返回一个非空值作为结果,Spring框架将会把那个结果作为一个新事件发送给我们。此外,我们可以通过在事件处理的结果中返回它们的集合来发布多个新事件。

7. 事件绑定事务

本节讨论使用@TransactionalEventListener注解。要了解更多关于事务管理,请查询使用Spring和JPA的事务

从Spring 4.2开始,框架提供了一个新的@TransactionalEventListener注解,它是@EventListener的扩展,允许将事件的监听器绑定到事务的一个阶段。

绑定可能发生在以下事务阶段:

  • AFTER_COMMIT(默认)用于在事务成功完成后触发事件。
  • AFTER_ROLLBACK --- 如果事务已回滚
  • AFTER_COMPLETION --- 如果事务已完成(AFTER_COMMIT和AFTER_ROLLBACK的别名)
  • BEFORE_COMMIT 用于在事务提交之前触发事件

这是一个事务事件监听器的快速实例:

JAVA 复制代码
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleCustom(CustomSpringEvent event) {
    System.out.println("Handling event inside a transaction BEFORE COMMIT.");
}

只有当事件生产者正在运行的事务存在且即将提交时,这个监听器才会被调用。

如果没有运行事务,除非我们通过设置fallbackExecution属性为true来覆盖这一点,否则根本不会发送事件。

8. 结论

在这篇简短的文章中,我们回顾了在Spring中处理事件的基础知识,包括创建一个简单的自定义事件,发布它然后在监听器中处理它。

我们还简要查看了如何在配置中启用事件的异步处理。

然后我们学习了Spring 4.2 引入的改进,例如注解驱动的监听器、更好的泛型支持以及事件与事务阶段的绑定。

如同往常,本文中呈现的代码可在GitHub上获得。这是一个基于Maven的项目,因此导入和运行应该很简单。

相关推荐
RemainderTime33 分钟前
Spring Boot脚手架集成Sa-Token实现生产级RBAC权限管理
java·spring boot·后端·系统架构
llz_1124 小时前
web-第二次课后作业
前端·后端·web
红尘散仙10 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记11 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
会编程的土豆11 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
喵个咪12 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball61612 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_25183645712 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao12 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
IT_陈寒14 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端