事件 (Event) 通知是 Spring 核心功能之一。
其中核心的类
org.springframework.context.ApplicationEvent
,该类定义了整个
Spring 应用中的各种事件。
在整个 Spring 的生命周期中就有很多的事件,像
Prepared
、Started
、Ready
等等事件。
比如说我们如果想要在 Spring
容器启动完成之后执行一些操作,那么就可以去监听
ApplicationReadyEvent
事件。
这些只是事件的定义,具体应该如何去监听呢?
很明显,我们需要一个 Listener
,具体到 Spring 中,就是
org.springframework.context.ApplicationListener
@FunctionalInterface public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { void onApplicationEvent(E event); }
|
可以看到,该监听器定义了一个泛型,该泛型一定是要继承自
ApplicationEvent
,该类就是去监听这些指定的事件。
思考下这其中用到的设计模式?
使用案例
1)定义一个事件
public class MyEvent extends ApplicationEvent { public MyEvent(Object source) { super(source); } }
|
2)创建事件对应的监听器
@Component public class MyEventListener implements ApplicationListener<MyEvent> { @Override public void onApplicationEvent(MyEvent event) { System.out.println("application listener source: " + event.getSource()); } }
|
3)定义启动器和对应的事件触发器
@SpringBootApplication public class App {
@Autowired private ApplicationEventPublisher eventPublisher;
public static void main(String[] args) { SpringApplication.run(App.class, args); }
@PostConstruct public void init() { eventPublisher.publishEvent(new MyEvent("execute init method ")); } }
|
启动执行结果:
可以看到事件成功被监听了。
上述只是其中的一种写法,还有一种事件监听方式是通过
@EventListener
注解的方式:
@Component public class AnnotationEventListener {
@EventListener(MyEvent.class) public void onMyEvent(MyEvent event) { System.out.println("annotation listener source: " + event.getSource()); } }
|
再次运行,但是上述监听器并没有生效!
为什么通过注解的方式没有监听到这个事件呢?
原因肯定跟 @PostConstruct
有关了。下面我们来 Debug
下源码。
原因分析
如何去分析一个注解的源码呢?
任何一个注解想要起到逻辑上的作用,肯定是有对应的注解处理器。我们可以直接去查看注解被那些地方使用到了。
可以看到这里的使用,打断点,调它!此外,还需要在两处监听的地方和发布事件的地方也打上断点,Debug
启动!
调试的过程可以看非常隐蔽Spring事件通知bug?源码动态分析技巧。
总结下来一张图:
可以看到,最大的问题就是加载监听器的顺序:
解决反思
1)尽量避免在容器启动过程中 @PostContrcut
方法中去发布事件,或者说统一使用 ApplicationListener
而非注解;
2)在 Spring 容器启动完成之后再去做事件发布。
@SpringBootApplication public class App implements CommandLineRunner {
@Autowired private ApplicationEventPublisher eventPublisher;
public static void main(String[] args) { SpringApplication.run(App.class, args); }
@Override public void run(String... args) throws Exception { eventPublisher.publishEvent(new MyEvent("execute init method ")); } }
|