前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >记一次使用spring事件机制失效排查修复

记一次使用spring事件机制失效排查修复

原创
作者头像
lyb-geek
发布2024-04-02 11:14:55
880
发布2024-04-02 11:14:55
举报
文章被收录于专栏:Linyb极客之路Linyb极客之路

前言

在日常业务开发中过程,我们有时候为了业务解耦,会利用spring的机制,就是利用spring提供的ApplicationListener、ApplicationEventMulticaster等核心API来实现。(注: 我这边列的是核心底层API接口,正常我们会用监听事件用@EventListener,发布事件用 applicationContext.publishEvent()或者applicationEventPublisher.publishEvent())

本文案例主要来自团队的小伙伴,在利用spring事件机制踩到的坑。直接以案例的形式来讲解

示例案例

案例场景:当项目启动时,从数据库加载学生数据,并放到本地缓存。为了业务解耦 ,团队小王采用了spring的事件驱动方式来实现。他的实现步骤如下

1、定义学生事件

代码语言:java
复制
public class StudentEvent extends ApplicationEvent {
    /**
     * Create a new {@code ApplicationEvent}.
     *
     * @param source the object on which the event initially occurred or with
     *               which the event is associated (never {@code null})
     */
    public StudentEvent(Object source) {
        super(source);
    }
}

2、创建学生事件发布

代码语言:java
复制
@Service
@RequiredArgsConstructor
public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> implements StudentService,InitializingBean {

    private final StudentDao studentDao;
    private final ApplicationContext applicationContext;

    @Override
    public void afterPropertiesSet() throws Exception {
        StudentEvent studentEvent = new StudentEvent(studentDao.listStudents());
        applicationContext.publishEvent(studentEvent);
    }
    @Override
    public List<StudentEntity> listStudents() {
        return studentDao.listStudents();
    }
}

3、创建事件监听

代码语言:java
复制
@Component
public class StudentCache {
    private Map<Integer, StudentEntity> studentMap = new ConcurrentHashMap<>();

    @EventListener
    public void listener(StudentEvent studentEvent){
        if(studentEvent.getSource() instanceof List){
            List<StudentEntity> studentEntityList = (List<StudentEntity>) studentEvent.getSource();
            if(studentEntityList != null){
                studentEntityList.forEach(studentEntity -> {
                    studentMap.put(studentEntity.getSId(), studentEntity);
                });
            }
        }

        System.out.println(studentMap);
    }

}

思考题:StudentCache能否正常接收到学生事件?

答案: 接收不到

问题解惑

首先我们要先确认事件监听的观察者,是何时加入事件监听容器?

我们可以从事件监听注解@EventListener入手,通过源码我们可以发现事件监听的观察者,是通过

代码语言:java
复制
org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated

方法调用加入到事件监听容器,而这个方法的调用时机是在

代码语言:java
复制
org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
d705d5d322c623d3e9912bd926087213_b81c20ccc2cd30e48cec68932876d896.png
d705d5d322c623d3e9912bd926087213_b81c20ccc2cd30e48cec68932876d896.png

即在所有非懒加载单例bean加载入spring单例池后才触发调用。而案例中,事件的发布放在

代码语言:java
复制
com.github.lybgeek.student.service.impl.StudentServiceImpl#afterPropertiesSet

实现,该方法会比afterSingletonsInstantiated更先执行,而此时事件监听容器还没有该事件的观察者,就会导致事件发布了,但是没有相应观察者进行监听

问题修复

方法有很多种,可以利用spring自带的事件,比如监听ContextRefreshedEvent事件后,再进行事件发布

代码语言:java
复制
@EventListener
    public void afterPropertiesSet(ContextRefreshedEvent contextRefreshedEvent) throws Exception {
        StudentEvent studentEvent = new StudentEvent(studentDao.listStudents());
        applicationContext.publishEvent(studentEvent);
    }

也可以利用spring其他扩展点,比如SmartInitializingSingleton,如果是springboot应用,还可以用CommandLineRunner或者ApplicationRunner

总结

本文修复问题的关键其实就是在于对spring一些扩展机制的优先调用顺序的了解

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 示例案例
  • 问题解惑
  • 问题修复
  • 总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档