前言
在日常的 Spring 开发中,我们经常会面临这样的需求:不同模块之间需要进行通信,但又不希望它们之间产生强耦合。这时候,Spring 提供的事件(Event)机制就显得尤为重要。
Spring 的事件机制是一种典型的**发布-订阅模式(Publish-Subscribe)**实现,可以帮助我们解耦模块逻辑,提高代码的灵活性与可维护性。通过自定义事件、事件监听器和事件发布器,我们可以让不同组件之间“松散对话”,而不是直接调用。
本篇文章将带你深入了解 Spring 中的事件机制,覆盖以下内容:
-
如何定义一个自定义事件类
-
如何编写一个事件监听器
-
如何在业务中发布事件
最重要的是,如何为这一套机制编写单元测试
通过这个实战案例,你将能够在项目中更自信地使用事件机制,为你的系统架构增添一份清晰与优雅。
🎯 适合读者
-
对 Spring 框架有一定基础,想进一步提升架构设计能力的开发者
-
希望了解如何编写可测试、低耦合代码的开发者
-
想通过 Spring Event 实现模块解耦、异步处理或扩展点机制的开发者
接下来 我们先看看Spring 自带的事件机制
在Spring中,事件机制是通过ApplicationEventPublisher和ApplicationListener接口实现的。ApplicationEventPublisher是用来发布事件的,而ApplicationListener则是监听这些事件的。
事件的流程:
- 发布事件:某个组件通过ApplicationEventPublisher来发布事件。
- 监听事件:另一个组件通过实现ApplicationListener接口来监听特定类型的事件。
- 广播事件:Spring容器会将事件广播给所有注册的监听器。
定义和发布事件
在Spring中,事件通常是继承自ApplicationEvent的类。我们可以自定义事件类,也可以使用Spring提供的内置事件。
假设我们有一个用户注册事件,当用户注册时,系统需要发送一个欢迎邮件。我们可以自定义一个事件类来封装这个行为:
import org.springframework.context.ApplicationEvent;
public class UserRegisteredEvent extends ApplicationEvent {
private final String username;
public UserRegisteredEvent(Object source, String username) {
super(source);
this.username = username;
}
public String getUsername() {
return username;
}
}
这个UserRegisteredEvent类继承自ApplicationEvent,并封装了一个username属性。
接着发布事件
发布事件的操作通常在服务层完成。我们通过注入ApplicationEventPublisher来发布事件:
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final ApplicationEventPublisher eventPublisher;
public UserService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void registerUser(String username) {
// 执行注册逻辑
System.out.println("用户 " + username + " 注册成功!");
// 发布事件
UserRegisteredEvent event = new UserRegisteredEvent(this, username);
eventPublisher.publishEvent(event);
}
}
在registerUser方法中,我们调用eventPublisher.publishEvent来发布UserRegisteredEvent事件
第三步、创建监听器
监听器用于接收并处理发布的事件。我们可以通过实现ApplicationListener接口或使用@EventListener注解来创建监听器。
-
使用ApplicationListener接口
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class UserRegisteredEventListener implements ApplicationListener<UserRegisteredEvent> {
@Override
public void onApplicationEvent(UserRegisteredEvent event) {
String username = event.getUsername();
System.out.println("监听到用户 " + username + " 注册事件,发送欢迎邮件!");
// 发送邮件的逻辑
}
} -
使用@EventListener注解
Spring 4.2及以上版本提供了更简洁的方式来监听事件,即使用@EventListener注解。这个注解可以放在任何@Component类中的方法上,Spring会自动将这个方法注册为事件监听器import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;@Component
public class UserRegisteredEventListener {@EventListener
public void handleUserRegisteredEvent(UserRegisteredEvent event) {
String username = event.getUsername();
System.out.println("监听到用户 " + username + " 注册事件,发送欢迎邮件!");
// 发送邮件的逻辑
}
}
上述需要深度依赖Spring框架 如果我们自己定义一套事件处理机制该如何操作
下面是一个从零开始实现自定义事件处理机制的例子。我们将通过发布事件、监听事件和广播事件的过程,手动实现一个类似Spring事件机制的功能。
自定义事件处理机制
我们将实现以下几个关键组件:
- 事件类:代表事件本身。
- 监听器接口:定义事件监听器的标准接口。
- 事件发布者:负责发布事件。
- 事件管理器:负责注册和通知事件监听器。
首先 定义一个事件接口
package com.example.event;
/**
*
* 事件接口
* @author Administrator
*/
public interface JmanusEvent {
}
接着定义一个事件发布者
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Administrator
*/
@Component
public class JmanusEventPublisher {
private static final Logger logger = LoggerFactory.getLogger(JmanusEventPublisher.class);
// 监听器无法动态注册,无需线程安全
private Map<Class<? extends JmanusEvent>, List<JmanusListener<? super JmanusEvent>>> listeners = new HashMap<>();
public void publish(JmanusEvent event) {
Class<? extends JmanusEvent> eventClass = event.getClass();
for (Map.Entry<Class<? extends JmanusEvent>, List<JmanusListener<? super JmanusEvent>>> entry : listeners
.entrySet()) {
// 这里父类也可以通知
if (entry.getKey().isAssignableFrom(eventClass)) {
for (JmanusListener<? super JmanusEvent> listener : entry.getValue()) {
try {
listener.onEvent(event);
}
catch (Exception e) {
logger.error("Error occurred while processing event: {}", e.getMessage(), e);
}
}
}
}
}
void registerListener(Class<? extends JmanusEvent> eventClass, JmanusListener<? super JmanusEvent> listener) {
List<JmanusListener<? super JmanusEvent>> jmanusListeners = listeners.get(eventClass);
if (jmanusListeners == null) {
List<JmanusListener<? super JmanusEvent>> list = new ArrayList<>();
list.add(listener);
listeners.put(eventClass, list);
}
else {
jmanusListeners.add(listener);
}
}
}
接着定义事件监听器的标准接口也就是 – 事件管理者
package com.example.event;
/**
* @author Administrator
* @desc jmanus 事件监听器
*/
public interface JmanusListener<T extends JmanusEvent> {
void onEvent(T event);
}
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.event;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.ResolvableType;
import org.springframework.stereotype.Component;
/**
* @author dahua
* @time 2025/7/15
* @desc jmanus 事件监听器注册
*/
@Component
public class JmanusListenerRegister implements BeanPostProcessor {
@Autowired
private JmanusEventPublisher jmanusEventPublisher;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof JmanusListener) {
ResolvableType resolvableType = ResolvableType.forClass(bean.getClass()).as(JmanusListener.class);
ResolvableType eventType = resolvableType.getGeneric(0);
Class<?> eventClass = eventType.resolve();
Class<? extends JmanusEvent> jmanusEventClass;
try {
jmanusEventClass = (Class<? extends JmanusEvent>) eventClass;
}
catch (Exception e) {
throw new IllegalArgumentException("The listener can only listen to JmanusEvent type");
}
jmanusEventPublisher.registerListener(jmanusEventClass, (JmanusListener) bean);
}
return bean;
}
}
接着定义自己的事件
package com.example.event;
/**
* @author Administrator
*/
public class ModelChangeEvent implements JmanusEvent{
private long createTime;
public ModelChangeEvent() {
this.createTime = System.currentTimeMillis();
}
public long getCreateTime() {
return createTime;
}
}
接下来 自定义2个事件监听器 -比如 注册成功发短信 发邮件
package com.example.event;
import com.example.event.JmanusListener;
import com.example.event.ModelChangeEvent;
import org.springframework.stereotype.Service;
/**
* @author Administrator
*/
@Service
public class MyCustom2EventListener implements JmanusListener<ModelChangeEvent> {
@Override
public void onEvent(ModelChangeEvent event) {
System.out.println("Listener2:Handled event with message: " + event.getCreateTime());
}
}
package com.example.event;
import org.springframework.stereotype.Service;
/**
* @author Administrator
*/
@Service
public class MyCustomEventListener implements JmanusListener<ModelChangeEvent>{
@Override
public void onEvent(ModelChangeEvent event) {
System.out.println("Listener:Handled event with message: " + event.getCreateTime());
}
}
运行结果
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.5.4)
2025–07–29T18:08:35.949+08:00 INFO 11520 —– [spring–event–demo] [ main] com.example.SpringEventDemoApplication : Starting SpringEventDemoApplication using Java 17.0.5 with PID 11520 (E:\\project\\java–web–core\\spring–event–demo\\target\\classes started by Administrator in E:\\project\\java–web–core)
2025–07–29T18:08:35.951+08:00 INFO 11520 —– [spring–event–demo] [ main] com.example.SpringEventDemoApplication : No active profile set, falling back to 1 default profile: "default"
2025–07–29T18:08:36.275+08:00 WARN 11520 —– [spring–event–demo] [ main] trationDelegate$BeanPostProcessorChecker : Bean 'jmanusEventPublisher' of type [com.example.event.JmanusEventPublisher] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto–proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [jmanusListenerRegister]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post–processed, declare it with ROLE_INFRASTRUCTURE.
2025–07–29T18:08:36.372+08:00 INFO 11520 —– [spring–event–demo] [ main] com.example.SpringEventDemoApplication : Started SpringEventDemoApplication in 0.711 seconds (process running for 1.14)
Listener:Handled event with message: 1753783716375
Listener2:Handled event with message: 1753783716375
源码案例
评论前必须登录!
注册