youyichannel

志于道,据于德,依于仁,游于艺!

0%

Spring Event 遇到的那些坑?

事件 (Event) 通知是 Spring 核心功能之一。

其中核心的类 org.springframework.context.ApplicationEvent,该类定义了整个 Spring 应用中的各种事件。

在整个 Spring 的生命周期中就有很多的事件,像 PreparedStartedReady 等等事件。

比如说我们如果想要在 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 "));
}
}