在 Spring 容器中通过ApplicationEvent类和 ApplicationListener接口来实现事件监听机制,每次Event 被发布到Spring容器中时都会通知该Listener。需要注意的是,Spring 的事件默认是同步的,调用 publishEvent 方法发布事件后,它会处于阻塞状态,直到Listener接收到事件并处理返回之后才继续执行下去。
实现一个事件监听的步骤:
(1)创建事件,也就是extends ApplicationEvent
@Getter
@Setter
@ToString
public class UserDTO extends ApplicationEvent{
private Integer userId;
private String name;
private Integer age;
public UserDTO(Object source){
super(source);
}
}
(2)定义监听器,也就是implements ApplicationListener
@Component
public class UserRegisterSmsListener{
// 通过注解实现监听器
@EventListener
public void handleUserEvent(UserDTO userDTO){
System.out.println("监听到用户注册,准备发送短信,user:"+userDTO.toString());
}
}
// 通过实现接口实现监听器
@Component
public class UserRegisterEmailListener implements ApplicationListener<UserDTO>{
@Override
public void onApplicationEvent(UserDTO userDTO){
System.out.println("监听到用户注册,准备发送邮件,user:" + userDTO.toString());
}
}
@Component
public class UserRegisterMessageListener implements ApplicationListener<UserDTO>{
@Override
public void onApplicationEvent(UserDTO userDTO){
System.out.println("监听到用户注册,给新用户发送首条站内短消息,user:" + userDTO.toString());
}
}
(3)注册服务,也就是将事件注册到监听器
public interface UserService{
void register();
}
@Service
public class UserServiceImpl implements UserService{
@Autowired
private ApplicationEventPublisher eventPublisher;
@Override
public void register(){
UserDTO userDTO = new UserDTO(this);
userDTO.setAge(18);
userDTO.setName("精灵王jinglingwang.cn");
userDTO.setUserId(1001);
System.out.println("register user");
eventPublisher.publishEvent(userDTO);
}
}
(4)测试即可
@Autowired
private UserService userService;
@Test
public void testUserEvent(){
userService.register();
}
spring提供了内置的几类的事件:
1、ContextClosedEvent
2、ContextRefreshedEvent
3、ContextStartedEvent
4、ContextStoppedEvent
5、RequestHandleEvent
在spring容器启动完成后会触发ContextRefreshedEvent事件。
上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext被初始化或者更新时发布。也可以在调用
ConfigurableApplicationContext 接口中的refresh()方法时被触发。
上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。
上述Spring内置的事件,都已经注册到了监听器上,所以直接使用即可
Spring项目启动后执行操作:ContextRefreshedEvent和ApplicationReadyEvent
一、Spring boot运行时,会发送以下事件
ApplicationStartingEvent
ApplicationEnvironmentPreparedEvent:当Environment已经准备好,在context 创建前
ApplicationContextInitializedEvent:在ApplicationContext 创建和ApplicationContextInitializer都被调用后,但是bean definition没有被加载前
ApplicationPreparedEvent:bean definition已经加载,但是没有refresh
ApplicationStartedEvent: context 已经被refresh, 但是application 和command-line 的runner都没有被调用
AvailabilityChangeEvent
ApplicationReadyEvent: application 和command-line 的runner都被调用后触发
AvailabilityChangeEvent
ApplicationFailedEvent: 启动失败触发
另外,会在ApplicationPreparedEvent之后和ApplicationStartedEvent之前发送
ContextRefreshedEvent
二、项目启动后需要执行某个操作
实现ApplicationListener接口
ApplicationEvent的子类可以是ApplicationReadyEvent或者ContextRefreshedEvent
ApplicationReadyEvent的示例
@Component
@Slf4j
public class ApplicationInit implements ApplicationListener {
// 项目启动后预热JSON
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
UserInfo userInfo = new UserInfo();
userInfo.setId(123L);
userInfo.setChannel("hello");
String userJson = JSON.toJSONString(userInfo);
JSON.parseObject(userJson, UserInfo.class);
}
}
三、ContextRefreshedEvent多次执行的问题
ApplicationListener (spring-context包) - ApplicationEvent- EventObject
ContextRefreshedEvent (spring-context包) - ApplicationContextEvent - ApplicationEvent - EventObject
ApplicationReadyEvent (spring-boot包) - SpringApplicationEvent - ApplicationEvent - EventObject
上述ApplicationListener、ContextRefreshedEvent、ApplicationReadyEvent都可以用来监听Spring容器是否启动完成,启动完成之后,可以进行某些操作。
Spring通过ApplicationListener接口来触发contextrefreshedevent事件
在开发时有时候需要在整个应用开始运行时执行一些特定代码,比如初始化环境,准备测试数据、加载一些数据到内存等等。
Listener是JavaWeb的三大组件(Servlet、Filter、Listener)之一,JavaWeb中的监听器主要用于监听:ServletContext、HttpSession、ServletRequest 的生命周期以及属性变化;在spring中也提供了监听器公开发人员使用;
其实现原理是设计模式之观察者模式,设计的初衷是为了系统业务之间进行解耦,以便提高系统可扩展性、可维护性。Listener 主要包括定义事件、事件监听、事件发布.
Java 中的事件机制
Java中提供了基本的事件处理基类:
EventObject:所有事件状态对象都将从其派生的根类;
EventListener:所有事件侦听器接口必须扩展的标记接口;
指定监听器的顺序
监听器的发布顺序是按照 bean 自然装载的顺序执行的,Spring 支持两种方式来实现有序
一、实现SmartApplicationListener接口指定顺序。
把上面三个Listener都改成实现SmartApplicationListener接口,并指定getOrder的返回值,返回值越小,优先级越高。
@Component
public class UserRegisterMessageListener implements SmartApplicationListener{
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType){
return eventType == UserDTO.class;
}
@Override
public boolean supportsSourceType(Class<?> sourceType){
return true;
}
@Override
public void onApplicationEvent(ApplicationEvent event){
System.out.println("监听到用户注册,给新用户发送首条站内短消息,user:" + event.toString());
}
@Override
public int getOrder(){
return -1;
}
}
另外两个监听器的改造省略,指定改造后的UserRegisterSmsListener返回order为0,UserRegisterEmailListener的getOrder返回1,测试输出结果如下:
二、使用注解@Order()
order的值越小,优先级越高
order如果不标注数字,默认最低优先级,因为其默认值是int最大值
@Component
public class UserRegisterSmsListener{
@Order(-2)
@EventListener
public void handleUserEvent(UserDTO userDTO){
System.out.println("监听到用户注册,准备发送短信,user:"+userDTO.toString());
}
}
测试输出结果如下:
register user
监听到用户注册,准备发送短信,user:UserDTO(userId=1001, name=精灵王jinglingwang.cn, age=18)
监听到用户注册,给新用户发送首条站内短消息,user:UserDTO(userId=1001, name=精灵王jinglingwang.cn, age=18)
监听到用户注册,准备发送邮件,user:UserDTO(userId=1001, name=精灵王jinglingwang.cn, age=18)
可以发现,短信监听器最先执行。
异步支持
Spring 事件机制默认是同步阻塞的,如果 ApplicationEventPublisher 发布事件之后他会一直阻塞等待listener 响应,多个 listener 的情况下前面的没有执行完后面的会一直被阻塞。这时候我们可以利用 Spring 提供的线程池注解 @Async 来实现异步线程.
一、使用 @Async 之前需要先开启线程池,在 启动类上添加 @EnableAsync 注解即可。
@EnableAsync
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
二、监听器使用异步线程
自定义异步线程池
@Configuration
public class AsyncConfig{
@Bean("asyncThreadPool")
public Executor getAsyncExecutor(){
System.out.println("asyncThreadPool init");
Executor executor = new ThreadPoolExecutor(
10,20,60L,TimeUnit.SECONDS
,new ArrayBlockingQueue<>(100),new MyThreadFactory());
return executor;
}
class MyThreadFactory implements ThreadFactory{
final AtomicInteger threadNumber = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r){
Thread t = new Thread(r);
t.setName("async-thread-"+threadNumber.getAndIncrement());
t.setDaemon(true);
return t;
}
}
}
指定监听器的线程池
@Component
public class UserRegisterSmsListener{
@Order(-2)
@Async("asyncThreadPool")
@EventListener
public void handleUserEvent(UserDTO userDTO){
System.out.println(Thread.currentThread().getName() + " 监听到用户注册,准备发送短信,user:"+userDTO.toString());
}
}
三、测试输出结果
register user
监听到用户注册,给新用户发送首条站内短消息,user:UserDTO(userId=1001, name=admol, age=18)
监听到用户注册,准备发送邮件,user:UserDTO(userId=1001, name=admol, age=18)
async-thread-0 监听到用户注册,准备发送短信,user:UserDTO(userId=1001, name=admol, age=18)