事件的监听与发布
有些时候,我们希望某件事情发生的时候能够触发一个事件,让这个事件帮我们做些事情。比如,在晚上十一点到晚上十二点这段时间,假如还有人在使用我们的软件,我们就触发一个事件播放一首美妙的音乐,希望用户听了之后能够做个好梦,美美睡上一觉。而这,我们需要做好三件事情:
1.定义一个事件。
2.定义一个事件监听器:如果事件监听器监听到事件发布了,就会触发事件监听器,播放一首美妙的音乐。
3.定义一个类:能够判断当前时间是否在晚上十一点与晚上十二点之间;如果是,就发布事件。
这样一来,我们期望的功能就能实现了。因此,我们首先需要做的,就是定义一个事件:
1 public class MusicEvent extends ApplicationEvent { 2 private String musicName = null; 3 4 public MusicEvent(Object source) { 5 super(source); 6 } 7 8 public String getMusicName() { 9 return this.musicName; 10 } 11 12 public void setMusicName(String musicName) { 13 this.musicName = musicName; 14 } 15 }
这是一个音乐事件,继承了ApplicationEvent类。ApplicationEvent是Spring提供的一个事件抽象类,凡是事件都应继承它。而我们的音乐事件继承ApplicationEvent抽象类后,做了这些事情:
1.定义一个构造函数。构造函数只是简单地调用父类的构造函数,把Object类型的参数传给父类。Object类型的参数是事件发布的事件源。
2.定义一个String类型的musicName属性,用于存放这个事件应该播放的音乐名。
于是,音乐事件定义好了。下一步,我们需要定义一个事件监听器:
1 public class MusicListener implements ApplicationListener<MusicEvent> { 2 @Override 3 public void onApplicationEvent(MusicEvent musicEvent) { 4 var musicName = musicEvent.getMusicName(); 5 var playingMessage = String.format("正在为您播放音乐《%s》", musicName); 6 System.out.println(playingMessage); 7 } 8 }
这是一个音乐事件监听器,实现了ApplicationListener接口。ApplicationListener是Spring提供的一个泛型接口,凡是事件监听器都应实现它;其签名如下:
1 @FunctionalInterface 2 public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { 3 void onApplicationEvent(E event); 4 }
这是一个函数式泛型接口。实现这个接口的时候,需要指定一个继承了ApplicationEvent类的子类。这样,当指定类型的事件发布的时候,事件监听器就能监听到指定类型的事件,调用onApplicationEvent方法进行处理。因此,音乐事件监听器实现ApplicationListener<MusicEvent>接口的onApplicationEvent方法的时候,做了这些事情:
1.调用musicEvent事件的getMusicName方法获取音乐名。
2.向控制台输出一段文字,告诉用户正在为您播放某首音乐。
这样一来,一旦音乐事件监听器监听到音乐事件发布了,就会触发音乐事件监听器,调用onApplicationEvent方法播放音乐。因此,下一步我们需要做的,就是发布事件:
1 public class MusicService implements ApplicationEventPublisherAware { 2 private ApplicationEventPublisher publisher = null; 3 4 @Override 5 public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { 6 this.publisher = publisher; 7 } 8 9 public void play(String musicName) { 10 var hourOfDay = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); 11 if (hourOfDay == 23) { 12 var musicEvent = new MusicEvent(this); 13 musicEvent.setMusicName(musicName); 14 publisher.publishEvent(musicEvent); 15 } 16 } 17 }
这段代码定义了一个类,该类实现了ApplicationEventPublisherAware接口。这是一个Aware接口,能让Spring容器注入ApplicationEventPublisher类型的Bean
ApplicationEventPublisher是个接口。在ApplicationContext实现的众多接口中,其中就有ApplicationEventPublisher接口。因此,ApplicationContext也是ApplicationEventPublisher类型的。Spring容器注入的ApplicationEventPublisher类型的Bean其实就是ApplicationContext本身。
问题在于,ApplicationEventPublisher是干嘛的?为什么需要注入一个ApplicationEventPublisher类型的Bean?
简单来说,ApplicationEventPublisher就是一个事件发布器,专门用于发布事件的。其定义了一个 void publishEvent(ApplicationEvent event) 方法,能够发布ApplicationEvent类型的事件。
因此,MusicService的play方法做了这件事情:获取当前时间的小时,判断当前时间的小时是否等于23,以此判断当前时间是不是在晚上十一点到晚上十二点之间。如果是,则进行事件的发布;否则什么也不做。事件发布的流程是这样的:
1.创建一个MusicEvent类型的事件。
2.以MusicEvent类型的事件为参数,调用ApplicationEventPublisher的publishEvent方法发布事件。
因此,我们只要调用MusicService的play方法就能发布事件了。从而触发音乐事件监听器,让音乐事件监听器播放音乐。为此,我们需要写个Java配置类,告诉Spring容器创建相应的Bean:
1 @Configuration 2 public class AppConfig { 3 @Bean("musicService") 4 public MusicService produceMusicService() { 5 return new MusicService(); 6 } 7 8 @Bean("musicListener") 9 public MusicListener produceMusicListener() { 10 return new MusicListener(); 11 } 12 }
之后,我们只需要在main函数里使用Java配置类创建一个Spring容器,从Spring容器里拿到ID为musicService的Bean之后调用Bean的play方法就能发布事件了:
1 public class Main { 2 public static void main(String[] args) { 3 try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) { 4 var musicService = context.getBean("musicService", MusicService.class); 5 musicService.play("Dream"); 6 } 7 } 8 }
我们调用ID为musicService的Bean的play方法发布了一个音乐事件,使音乐事件监听器监听到音乐事件之后播放那首名为《Dream》的音乐。
需要注意的是,事件的发布与监听是同步的。也就是说,play方法发布事件之后需要等到监听器完成事件的处理之后才能接着往下执行。还有,现在的事件是通过继承ApplicationEvent抽象类实现的,事件监听器则是通过实现ApplicationListener接口实现的。我们也能使用@EventListener注解做同样的事情,只要对MusicEvent和MusicListner稍做改动就行。
具体而言,MusicEvent已经不需要继承ApplicationEvent抽象类了。可以把它改成一个普通的类:
1 public class MusicEvent { 2 private String musicName = null; 3 4 public String getMusicName() { 5 return this.musicName; 6 } 7 8 public void setMusicName(String musicName) { 9 this.musicName = musicName; 10 } 11 }
同样的,MusicListener也不需要实现ApplicationListener接口。可以把它改成一个普通的类,再把@EventListener注解加到MusicListener的方法上即可:
1 public class MusicListener { 2 @EventListener 3 public void onMusicEvent(MusicEvent musicEvent) { 4 var musicName = musicEvent.getMusicName(); 5 var playingMessage = String.format("正在为您播放音乐《%s》", musicName); 6 System.out.println(playingMessage); 7 } 8 }
MusicListener再也没有实现ApplicationListener接口,而是定义了onMusicEvent方法,再在方法上面添加@EventListener注解,就能监听MusicEvent事件。另外,如果希望监听器异步处理发布的事件,可往监听器方法添加@Async注解,如下所示:
1 public class MusicListener { 2 @Async 3 @EventListener 4 public void onMusicEvent(MusicEvent musicEvent) { 5 var musicName = musicEvent.getMusicName(); 6 var playingMessage = String.format("正在为您播放音乐《%s》", musicName); 7 System.out.println(playingMessage); 8 } 9 }
于是,关于如何添加自定义事件的介绍就告一段落了。需要了解的是,Spring还内置了些事件:
1.ContextRefreshedEvent:Spring容器完成初始化或刷新之后就会发布这个事件。
2.ContextStartedEvent:Spring容器调用start()方法启动之后就会发布这个事件。
3.ContextStoppedEvent:Spring容器调用stop()方法停止之后就会发布这个事件。
4.ContextClosedEvent:Spring容器调用close()方法关闭之后就会发布这个事件。
5.RequestHandledEvent:Web请求完成处理之后就会发布这个事件。
6.ServletRequestHandledEvent:和RequestHandledEvent一样,也是Web请求完成处理之后就会发布这个事件。只是这个事件多了一些Servlet的上下文信息。
使用这些内置事件的时候,只要实现这些事件的监听器即可。这里不再介绍。
至此,关于事件的监听与发布的介绍也就告一段落了。下章,我们将会开始介绍面向切面编程。欢迎大家继续阅读,谢谢大家!