Event事件机制
Spring事件机制基于观察者模式实现了事件发布和监听的功能,Spring有一些框架内部的事件,我们也可以自定义事件。使用事件机制的好处是能够进一步解耦业务逻辑,实现低耦合、无侵入式的业务逻辑处理,也可以实现异步处理等高级功能。
基本概念
事件:Spring中的事件ApplicationEvent继承JDK的java.util.EventObject抽象类,该类包含了一个source属性保存了事件源对象的引用,以及时间发生的时间戳,Spring内置的事件和我们自定义的事件需要继承ApplicationEvent这个类来实现。
事件发布者:用于发布事件的接口,Spring框架中我们可以调用applicationContext.publishEvent()发布事件。
事件监听器:事件监听器可以使用@EventListener注解或实现ApplicationListener接口来定义,注意它必须是一个Spring的Bean,否则Spring框架是无法找到指定的监听器对象的。
事件机制的使用
监听Spring内置事件
Spring内置的事件和发布者都是Spring实现的,因此我们编写一个事件监听器即可。
package com.gacfox.demo.demoboot;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class DemoComponent {
@EventListener
public void onStarted(ApplicationStartedEvent event) {
System.out.println("Spring启动完成");
}
}
代码中我们使用了@EventListener注解标注了一个方法,Spring会根据方法的参数类型,在对应事件发生时回调方法。如果参数类型使用ApplicationEvent,则所有类型的事件该方法都会接收到。
此外要注意,如果有多个方法监听同一个事件,事件发生时所有方法都会被回调,不会出现只有一个方法被调用的情况。
Spring中常用的内置事件有如下几种:
| 事件类型 | 说明 |
|---|---|
| ContextRefreshedEvent | 当Spring容器初始化或刷新时触发,即applicationContext的refresh()方法被调用后。 |
| ContextStartedEvent | 当Spring容器停止时触发,即applicationContext的start()方法被调用后。 |
| ContextStoppedEvent | 当Spring容器关闭时触发,即applicationContext的stop()方法被调用后。 |
| ContextClosedEvent | 当Spring容器启动时触发,即applicationContext的close()方法被调用后。 |
| RequestHandledEvent | 用于SpringMVC环境,当DispatcherServlet处理请求完成后触发。 |
自定义事件
自定义事件和使用内置事件类似,只不过需要我们手动创建EventObject和发射事件,下面是一个自定义事件的例子。
RegistrationEvent.java
package com.gacfox.demo.demoboot;
import org.springframework.context.ApplicationEvent;
public class RegistrationEvent extends ApplicationEvent {
public RegistrationEvent(Object source) {
super(source);
}
}
DemoComponent.java
package com.gacfox.demo.demoboot;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class DemoComponent {
@EventListener
public void onRegistration(RegistrationEvent event) {
System.out.println("用户注册了");
}
}
IndexController.java
package com.gacfox.demo.demoboot;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class IndexController implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@GetMapping("/")
public void register() {
applicationContext.publishEvent(new RegistrationEvent(this));
}
}
代码中,RegistrationEvent是我们自定义的事件,它继承了ApplicationEvent,DemoComponent是我们的事件监听器,最后IndexController是事件是发射器,该类实现了ApplicationContextAware接口用来获取applicationContext对象,在控制器方法register()被触发时就会通过applicationContext对象发射事件,并由DemoComponent的onRegistration()方法接收。
异步事件
上面介绍的自定义事件监听例子其实还是一个同步式的代码,如果我们的EventListener处理事件的耗时较长,我们的IndexController也必须等待它处理完成后才能返回。实际上,Spring的事件机制也支持异步方式进行处理。
以SpringBoot为例,使用异步事件首先我们需要在启动类上标注@EnableAsync开启异步支持,此时在我们的EventListener方法上标注@Async,它就可以异步执行了。
@Async
@EventListener
public void onRegistration(RegistrationEvent event) {
// ...
}
我们可以通过打印线程ID和线程名来验证是否实现了异步执行,如果异步执行,事件发布的线程和处理事件的线程应该并非同一个。
注意:由于Java的ServletAPI从设计之初就采用的是多线程并发模型,我们对请求进行的处理都是串行的,发生在同一个线程中,因此许多API都是依赖ThreadLocal构建的,比如SpringMVC的RequestContextHolder、日志框架的MDC等。如果引入异步处理这些API可能就会失效,我们需要考虑到这些潜在的问题。