什么是Spring事件?何时使用它?如何使用它?
什么是Spring事件?
Spring事件机制是Spring框架的一个特性,它允许组件以松散耦合的方式相互通信。它基于观察者设计模式,其中一个对象(主题)维护其依赖项列表(观察者),并自动通知它们任何状态更改。在Spring中,事件由ApplicationEvent类或其子类的实例表示。
Spring中观察者模式的核心类如下:
- ApplicationListener:扩展自JDK的EventListener的应用程序监听器或观察者。当所侦听的事件发生时,此类中唯一的方法onApplication()将被调用。
- ApplicationEvent:扩展自JDK的EventObject的抽象事件类。Spring和SpringBoot中的所有事件都是此类的子类。
- ApplicationEventMulticaster:事件注册和广播中心,用于注册侦听器和广播事件。
- ApplicationContext:Spring的IOC容器,它也是一个发布者,因为ApplicationContext扩展了ApplicationEventPublisher,可以通过publishEvent(Object event)方法发布事件。
这些类共同形成了Spring观察者模式的核心,允许在框架内进行灵活的事件驱动编程。
在Spring事件机制下,Spring应用程序中的组件可以相互通信,而不需要直接了解彼此。这允许采用更灵活和松散耦合的架构,其中可以轻松添加、删除或替换组件,而不会影响系统中的其他组件。
下图显示了Spring源代码中的事件发布工作流程。
好的,让我们来看看Spring事件的核心类:
ApplicationEvent
public abstract class ApplicationEvent extends EventObject {
/** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = 7099057708183571937L;
/** System time when the event happened. */
private final long timestamp;
/**
* Create a new {@code ApplicationEvent} with its {@link #getTimestamp() timestamp}
* set to {@link System#currentTimeMillis()}.
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
* @see #ApplicationEvent(Object, Clock)
*/
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
/**
* Create a new {@code ApplicationEvent} with its {@link #getTimestamp() timestamp}
* set to the value returned by {@link Clock#millis()} in the provided {@link Clock}.
* <p>This constructor is typically used in testing scenarios.
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
* @param clock a clock which will provide the timestamp
* @since 5.3.8
* @see #ApplicationEvent(Object)
*/
public ApplicationEvent(Object source, Clock clock) {
super(source);
this.timestamp = clock.millis();
}
/**
* Return the time in milliseconds when the event occurred.
* @see #ApplicationEvent(Object)
* @see #ApplicationEvent(Object, Clock)
*/
public final long getTimestamp() {
return this.timestamp;
}
}
ApplicationEvent是Spring框架中的一个抽象类,用作Spring中所有应用程序级别事件的基类。它为在Spring框架中发布和使用事件提供了一致的API。
ApplicationEvent类提供了几个构造函数,允许子类使用必要的信息初始化事件。这包括事件的来源,通常是发布事件的对象,以及事件可能需要携带的任何其他数据。
通过扩展ApplicationEvent类,开发人员可以创建自己的自定义事件,并使用Spring事件机制发布它们。这允许采用灵活和可扩展的架构,其中不同的组件可以通过事件相互通信,而不是通过直接方法调用或其他形式的耦合。
上图中每个ApplicationEvent的主要应用场景如下:
- ServletRequestHandledEvent:此事件由Spring Web MVC框架在@RequestMapping方法处理请求后发布,类似于RequestHandledEvent。但是,与RequestHandledEvent不同,ServletRequestHandledEvent提供了有关请求和响应对象的更详细信息。
- PayloadApplicationEvent:spring-context模块类,早期Spring仅支持发布ApplicationEvent类型事件,自从Spring 4.2可以发布非ApplicationEvent类型事件,当事件参数将被包装到PayloadApplicationEvent中,然后发布。
- ApplicationContextEvent:这是Spring框架中的一种事件类型,当应用程序上下文的状态发生变化时由应用程序上下文发布。此事件用于跟踪应用程序上下文的生命周期,并在初始化、刷新或关闭上下文时执行某些操作。
- ContextStartedEvent:此事件在启动应用程序上下文时发布,通常在上下文上调用start()方法时。
- ContextStoppedEvent:此事件在停止应用程序上下文时发布,通常在上下文上调用stop()方法时。
- ContextRefreshedEvent:此事件在初始化或刷新应用程序上下文时发布。这通常发生在应用程序启动时,或者在手动刷新上下文时发生。
- ContextClosedEvent:此事件在关闭应用程序上下文时发布,通常在上下文上调用close()方法时。
通过侦听这些事件,开发人员可以在应用程序上下文生命周期的不同阶段执行某些操作。例如,在刷新上下文时初始化bean,在启动时启动后台线程,或在关闭上下文时释放资源。
SpringBoot中的事件
Spring Boot基于Spring框架,并提供了额外的功能和约定,使得创建面向生产的应用程序更加容易。Spring Boot利用Spring事件的一种方式是提供预构建的事件监听器,可以直接使用。
例如,Spring Boot提供了一个SpringApplication类,用于引导应用程序上下文并启动应用程序。此类还在应用程序启动和关闭过程中发布了几个事件,包括:
- ApplicationStartingEvent:此事件在应用程序上下文创建之前的应用程序启动过程的最开始时发布。
- ApplicationEnvironmentPreparedEvent:此事件在创建应用程序上下文之后,但在准备环境之前发布。
- ApplicationPreparedEvent:此事件在准备环境之后,但在刷新应用程序上下文之前发布。
- ApplicationStartedEvent:此事件在刷新应用程序上下文并启动应用程序时发布。
- ApplicationReadyEvent:此事件在应用程序启动并准备好服务请求时发布。
- ApplicationFailedEvent:如果应用程序启动失败,则发布此事件。
当Spring Boot应用程序启动时,它使用一个称为EventPublishingRunListener的类来向Spring的事件机制发布事件。该类是ApplicationListener接口的Spring Boot特定实现,用于侦听特定的应用程序事件,并将它们发布到Spring的事件机制。
当EventPublishingRunListener从SpringApplication接收到开始状态事件时,它使用SimpleApplicationEventMulticaster向所有已注册的Spring侦听器广播事件。这是Spring事件模型使用的相同机制,它允许Spring Boot利用Spring提供的现有基础设施来管理事件。
因此,总之,Spring Boot的事件机制利用Spring事件模型,使用Spring Boot特定的ApplicationListener接口实现向现有的Spring事件基础设施发布事件。好的,让我们来看看核心源代码:
EventPublishingRunListener
class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
}
@Override
public int getOrder() {
return 0;
}
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
multicastInitialEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
multicastInitialEvent(
new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
multicastInitialEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware contextAware) {
contextAware.setApplicationContext(context);
}
context.addApplicationListener(listener);
}
multicastInitialEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}
@Override
public void started(ConfigurableApplicationContext context, Duration timeTaken) {
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, timeTaken));
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
}
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
if (context != null && context.isActive()) {
// Listeners have been registered to the application context so we should
// use it at this point if we can
context.publishEvent(event);
}
else {
// An inactive context may not have a multicaster so we use our multicaster to
// call all the context's listeners instead
if (context instanceof AbstractApplicationContext abstractApplicationContext) {
for (ApplicationListener<?> listener : abstractApplicationContext.getApplicationListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
this.initialMulticaster.multicastEvent(event);
}
}
private void multicastInitialEvent(ApplicationEvent event) {
refreshApplicationListeners();
this.initialMulticaster.multicastEvent(event);
}
private void refreshApplicationListeners() {
this.application.getListeners().forEach(this.initialMulticaster::addApplicationListener);
}
private static class LoggingErrorHandler implements ErrorHandler {
private static final Log logger = LogFactory.getLog(EventPublishingRunListener.class);
@Override
public void handleError(Throwable throwable) {
logger.warn("Error calling ApplicationEventListener", throwable);
}
}
}
正如你所看到的,EventPublishingRunListener实现了_SpringApplicationRunListener_接口,该接口定义了在Spring Boot应用程序启动过程中调用的一组方法。
当构造EventPublishingRunListener时,它会创建一个新的SimpleApplicationEventMulticaster并添加所有已注册到SpringApplication实例的应用程序监听器。
EventPublishingRunListener中的每个方法对应于Spring Boot启动过程中的特定事件。
例如:
- 当应用程序上下文已启动时,将调用started()方法
- 当应用程序准备好接收请求时,将调用running()方法。
在这些方法中,EventPublishingRunListener使用在构造期间创建的ApplicationEventMulticaster实例将适当的事件发布到Spring Framework的事件基础架构中。
总的来说,EventPublishingRunListener是Spring Boot启动过程中的关键组件,它允许将事件发布到Spring Framework的事件机制中,使其他组件和应用程序可以监听和响应这些事件。
那么,我们可以从Spring事件机制中获得什么呢?
何时使用
当你希望解耦应用程序中的组件并允许它们相互通信而不直接了解彼此时,应使用Spring事件机制。这可以使你的应用程序更加灵活和易于维护。你可以使用事件来触发响应状态更改的操作,例如更新缓存或发送通知。你还可以使用事件在应用程序中传播异常或其他错误条件。
下面我列出了许多可能会引起你兴趣的场景:
- 基于某些事件触发操作:你可以侦听UserLoggedInEvent并执行某些操作,例如记录事件、更新用户的最后登录时间戳或向用户发送欢迎消息。
- 解耦组件:如果你有一个生成通知的组件,你可以定义一个事件,当生成新通知时触发该事件,并让其他组件侦听此事件以执行其他操作。
- 构建可扩展的系统:你可以定义一组事件,当应用程序中发生某些操作时可以触发这些事件,并允许开发人员注册其自己的监听器以执行其他操作。
- 审计和日志记录:你可以定义一个事件,当更新数据库记录时触发该事件,并有一个侦听器记录更改和进行更改的用户。
- 缓存失效:你可以定义一个事件,当更新用户的配置文件时触发该事件,并有一个侦听器使缓存版本的配置文件失效,以便下次请求时从数据库中检索更新的版本。
- 与外部系统集成:你可以定义一个事件,当在应用程序中放置新订单时触发该事件,并有一个侦听器通过消息队列或API将订单信息发送到外部系统。
- 实施业务规则:你可以定义一个事件,当放置新订单时触发该事件,并有一个侦听器检查订单是否符合一组业务规则以确定是否可以处理。
- 实时更新:你可以定义一个事件,当在博客文章上发布新评论时触发该事件,并有一个侦听器以新评论实时更新页面。
- 错误处理:你可以定义一个事件,当应用程序中出现错误时触发该事件,并有一个侦听器记录错误并向支持团队发送电子邮件。
- 功能切换:你可以定义一个事件,当发布新功能时触发该事件,并有一个侦听器为某个用户子集启用该功能。
- 用户通知:你可以定义一个事件,当用户的订阅即将到期时触发该事件,并有一个侦听器向用户发送通知,提醒他们续订他们的订阅。
- 工作流自动化:你可以定义一个事件,当接收到新订单时触发该事件,并有一个侦听器自动将订单分配给适当的团队进行处理。
了解了什么和何时,接下来让我们来看看如何使用。
如何使用
-
创建一个自定义事件,扩展自ApplicationEvent
-
定义事件监听器,实现_ApplicationListener_
-
在Spring容器中发布事件 代码
-
创建你自己的事件
public class CustomizeEvent extends ApplicationEvent {
private String msg;
public CustomizeEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
- 定义一个监听器,有两种推荐的方法:
- 注释
- 编程方式
// Annotation
@Component
@Slf4j
public class AnnotationCustomizeListener {
@EventListener
public void eventListener(CustomizeEvent event){
log.info("Annotation Customized Listener catch the Event and the message: {}", event.getMsg());
}
}
// Programmatic
@Component
@Slf4j
public class CustomizeListener implements ApplicationListener<CustomizeEvent> {
@Override
public void onApplicationEvent(CustomizeEvent event) {
log.info("CustomizeListener catch the Event and the message: {}", event.getMsg());
}
}
- 通过以下方式发布事件:
- ApplicationContext(它扩展自ApplicationEventPublisher,因此仍然使用相同的publishEvent()方法)
- ApplicationEventPublisher
//publish when start the application.
@Component
public class CustomizePublisher {
@Autowired
ApplicationContext applicationContext;
public void publish(String msg) {
applicationContext.publishEvent(new CustomizeEvent(this, msg));
}
}
//publish when some api been called.
@RestController
@RequestMapping("/event")
public class EventController {
@Autowired private ApplicationEventPublisher publisher;
@Autowired private ApplicationContext applicationContext;
@RequestMapping("/t1")
public void sendEvent1() {
applicationContext.publishEvent(new CustomizeEvent(new Object(), "Hi applicationContext publish Event"));
}
/**
* ApplicationContext extends from ApplicationEventPublisher so it still use same the publishEvent() method
*/
@RequestMapping("/t2")
public void sendEvent2() {
publisher.publishEvent(new CustomizeEvent(new Object(), "Hi ApplicationEventPublisher publish Event"));
}
}
进阶
- 排序侦听器:在某些情况下,可能需要确保在其他侦听器之前或之后执行某些侦听器。Spring提供了@Order注释,以指定事件侦听器的执行顺序。
默认情况下,侦听器按注册顺序执行。要使用@Order,只需使用@Order注释注释事件侦听器方法并指定一个值。具有最低值的侦听器将首先执行,具有相同值的侦听器将按注册顺序执行。
@Component
@Slf4j
public class AnnotationOrderListener {
@EventListener
@Order(2)
public void order2(CustomizeEvent event){
log.info("CustomizeEvent with order {2}");
}
@EventListener
@Order(1)
public void order1(CustomizeEvent event){
log.info("CustomizeEvent with order {1}");
}
}
- 异步侦听器Spring还支持异步事件侦听器。这意味着事件侦听器可以在单独的线程中执行,这可以在某些情况下提高性能。
要使事件侦听器异步执行,只需使用@Async注释注释侦听器方法。这将导致该方法在由Spring的任务执行器管理的单独线程中执行。
@EnableAsync
@SpringBootApplication
public class SpringEventApplication {
public static void main(String[] args) {
SpringApplication.run(SpringEventApplication.class, args);
}
}
@Component
@Slf4j
public class AnnotationAsynchronousListener {
@EventListener
@Async
public void AsyncListener(CustomizeEvent event){
log.info("CustomizeEvent Asynchronous Listener");
}
}
使用异步事件侦听器时要小心,因为它们可能会引入复杂性和潜在问题。例如,如果事件侦听器修改共享状态,则必须确保对共享状态的访问是同步的或正确管理的,以避免竞态条件。
此外,如果事件侦听器引发异常,则可能无法由调用线程正确捕获和处理,这可能会导致意外行为。
与所有异步编程一样,重要的是要了解潜在问题并仔细设计代码。
最佳实践
- 使事件与侦听器解耦:将事件定义在与侦听器不同的模块或包中,以确保对一个模块的更改不会影响另一个模块。
- 为长时间运行的任务使用异步侦听器:如果侦听器执行耗时的任务,请考虑使用异步侦听器以防止主线程阻塞。
- 在适当的情况下使用事件驱动的体系结构:事件是实现事件驱动的体系结构的一种绝佳方式,其中服务可以通过事件而不是直接调用进行通信。这可以使你的系统更具可扩展性和灵活性。
- 使用上下文事件初始化资源:可以使用上下文事件,例如ContextRefreshedEvent,来初始化应用程序需要的资源,例如数据库连接或缓存。
- 避免将事件用于关键功能:事件应用于非关键功能,例如日志记录、审计或触发后台任务。避免将事件用于关键功能,例如事务管理或安全检查。
- 使用泛型以确保类型安全:在定义自定义事件时,请使用泛型以确保类型安全并防止类转换异常。
- 测试你的事件侦听器:编写单元测试以确保事件侦听器按预期工作并正确处理事件。
通过遵循这些最佳实践,你可以确保你对Spring事件的使用是有效、高效和可维护的。总的来说,Spring框架的事件机制是一个非常强大的功能,为Java应用程序提供了更灵活和可维护的架构。
评论(0)