目录
- 前言
- 1. 基本知识
- 2. Demo
- 3. 实战代码
前言
🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF
基本的Java知识推荐阅读:
- java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)
- 【Java项目】实战CRUD的功能整理(持续更新)
1. 基本知识
ApplicationEventPublisher
是 Spring 框架中一个功能接口(@FunctionalInterface
),用于发布事件
是 Spring 的事件驱动模型的核心部分,开发者可以通过实现这个接口或通过 Spring 提供的现成实现来发布和管理事件
基本知识如下:
-
事件驱动模型:
Spring 提供了一个内置的事件模型,通过事件发布者(ApplicationEventPublisher
)和事件监听器(@EventListener
或ApplicationListener
)
事件可以是框架提供的(例如:ContextRefreshedEvent
),也可以是用户自定义的事件 -
@FunctionalInterface 注解:
声明此接口是函数式接口,只有一个抽象方法:publishEvent(Object event)
可以使用 lambda 表达式或方法引用来实现 -
事件类型:
支持两种事件对象:
ApplicationEvent 类型
非 ApplicationEvent 类型(会被包装成 PayloadApplicationEvent) -
事件传播特点:
异步/同步:事件的传播方式取决于事件监听器的实现,发布者本身不决定事件的执行方式
高效性建议:事件监听器应尽量快速完成任务,对于耗时操作建议使用异步处理
主要方法解析
publishEvent(ApplicationEvent event)
接收 ApplicationEvent 类型事件
实际是将事件转换为 Object 类型后调用 publishEvent(Object event) 方法publishEvent(Object event)
接收任何对象类型的事件
如果事件不是 ApplicationEvent 类型,会封装为 PayloadApplicationEvent
2. Demo
完整的可执行 Spring Boot 示例,展示了如何使用 ApplicationEventPublisher 实现事件发布和监听功能
这是一个基于 Spring Boot 核心功能的示例,无需 Spring Cloud
项目结构如下:
src/main/java/com/example/demo
├── DemoApplication.java
├── CustomEvent.java
├── CustomEventListener.java
├── EventPublisherService.java
截图如下:
主体流程如下:
+-----------------------+
| DemoApplication | <--- 运行时触发事件发布
+-----------------------+
|
v
+----------------------------+ 1. 通过依赖注入调用服务类
| EventPublisherService |
+----------------------------+
|
v
+-----------------------+ 2. 使用 ApplicationEventPublisher 发布事件
| ApplicationEventPublisher | -------> 发布 CustomEvent
+-----------------------+
|
v
+----------------------+
| CustomEvent | <--- 发布事件包含消息
+----------------------+
|
v
+-----------------------+
| CustomEventListener |
+-----------------------+
|
v
+-----------------------------+
| 打印 "Received custom event" |
+-----------------------------+
DemoApplication
(主类)
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired
private EventPublisherService eventPublisherService;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(String... args) {
// 发布自定义事件
eventPublisherService.publishEvent("Hello, ApplicationEventPublisher!");
}
}
CustomEvent
(事件类)
继承自 ApplicationEvent
可以封装任何自定义属性,例如 message
package com.example.demo;
import org.springframework.context.ApplicationEvent;
public class CustomEvent extends ApplicationEvent {
private final String message;
public CustomEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
CustomEventListener
(事件监听器)
使用@EventListener
注解监听特定事件
方法参数即为监听的事件类型
package com.example.demo;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class CustomEventListener {
@EventListener
public void handleCustomEvent(CustomEvent event) {
System.out.println("Received custom event: " + event.getMessage());
}
}
EventPublisherService
(事件发布服务)
用于在应用上下文中发布事件
Spring 框架会自动分发事件到匹配的监听器
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class EventPublisherService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void publishEvent(String message) {
// 构造并发布事件
CustomEvent event = new CustomEvent(this, message);
eventPublisher.publishEvent(event);
System.out.println("Event published: " + message);
}
}
运行 DemoApplication 后,控制台输出如下:
Event published: Hello, ApplicationEventPublisher!
Received custom event: Hello, ApplicationEventPublisher!
截图如下:
扩展功能
- 异步事件监听:在监听方法上加 @Async,并在主类中启用异步:
@SpringBootApplication
@EnableAsync
public class DemoApplication { ... }
-
多监听器:可以定义多个监听器监听相同事件,Spring 会自动分发到每个监听器
-
自定义事件的继承:可以继承 CustomEvent 定义不同类型的事件,以实现事件的多态性
3. 实战代码
比如发送邮件,如果单纯跟接口进行绑定,代码后续扩展优化会非常冗余!
使用 事件机制(applicationContext.publishEvent 和监听器)
有如下好处:
- 解耦业务逻辑
通过事件机制,发送邮件的逻辑和业务逻辑分离:
业务代码只需要关心触发“发送邮件”这一行为(发布事件)
实际的邮件发送逻辑由监听器单独处理
这种解耦方式的优势:
- 更清晰的代码职责:业务代码不会夹杂具体的邮件发送逻辑
- 方便扩展:如果未来需要增加更多处理逻辑(如记录日志、重试机制等),可以直接扩展监听器,而不用修改业务代码
而且最主要的是:
- 支持异步操作
在监听器上添加了@Async
注解,可以让事件的处理逻辑异步执行:
@Async
@EventListener
public void onMessage(MailSendMessage message) {
log.info("[onMessage][消息内容({})]", message);
mailSendService.doSendMail(message);
}
这样,主线程可以迅速完成主要业务逻辑(如创建发送日志)并返回,而不用等待邮件发送完成
对于高并发场景,这种异步机制非常有用
如果直接在业务方法中调用发送函数,就无法方便地实现异步处理,可能会导致:
- 性能问题:主线程被邮件发送操作阻塞
- 用户体验问题:如果邮件发送需要较长时间,业务响应时间会变长
- 灵活性和可扩展性
使用事件机制后,邮件发送的逻辑变成了“事件订阅者”:
可以轻松增加或移除其他监听器,而不会影响现有的业务代码
例如,除了发送邮件外,还可以添加监听器发送短信、推送通知、记录操作日志等
可以根据不同的事件类型(不同的事件类)触发不同的逻辑
上述功能比较抽象,以实际代码为例:
如果做一个接口,发送邮件,信息量很大的时候,需要等这个邮件信息,待结果返回,才可以给客户!
- 发送邮件的代码和业务代码紧密耦合,修改或扩展会很麻烦
- 如果要实现异步发送,还需要自己额外管理线程池或异步任务,增加复杂度
- 如果未来需要在发送邮件时附加其他逻辑(如发送通知),业务代码会变得越来越复杂
public Long sendSingleMail(...) {
...
// 直接发送邮件
mailSendService.doSendMail(...);
return sendLogId;
}
以下是结合ApplicationEventPublisher,下述代码以ruoyi-vue-pro代码为例子进行讲解
整体目录如下:
使用 @EventListener 注解监听特定事件
方法参数即为监听的事件类型
方法参数即为监听的事件类型
方法参数即为监听的事件类型
/**
* 针对 {@link MailSendMessage} 的消费者
*/
@Component
@Slf4j
public class MailSendConsumer {
@Resource
private MailSendService mailSendService;
@EventListener
@Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步
public void onMessage(MailSendMessage message) {
log.info("[onMessage][消息内容({})]", message);
mailSendService.doSendMail(message);
}
}
对应的实体类:
@Data
public class MailSendMessage {
/**
* 邮件日志编号
*/
@NotNull(message = "邮件日志编号不能为空")
private Long logId;
/**
* 接收邮件地址
*/
@NotNull(message = "接收邮件地址不能为空")
private String mail;
/**
* 邮件账号编号
*/
@NotNull(message = "邮件账号编号不能为空")
private Long accountId;
/**
* 邮件发件人
*/
private String nickname;
/**
* 邮件标题
*/
@NotEmpty(message = "邮件标题不能为空")
private String title;
/**
* 邮件内容
*/
@NotEmpty(message = "邮件内容不能为空")
private String content;
// private File files;
}
对应发送消息:
@Slf4j
@Component
public class MailProducer {
@Resource
private ApplicationContext applicationContext;
/**
* 发送 {@link MailSendMessage} 消息
*
* @param sendLogId 发送日志编码
* @param mail 接收邮件地址
* @param accountId 邮件账号编号
* @param nickname 邮件发件人
* @param title 邮件标题
* @param content 邮件内容
*/
public void sendMailSendMessage(Long sendLogId, String mail, Long accountId,
String nickname, String title, String content) {
MailSendMessage message = new MailSendMessage()
.setLogId(sendLogId).setMail(mail).setAccountId(accountId)
.setNickname(nickname).setTitle(title).setContent(content);
applicationContext.publishEvent(message);
}
}
实际主体代码是直接使用发送消息,不用对接接收消息,接收消息是直接监听就好!
业务逻辑代码: