前言
在日常业务开发中过程,我们有时候为了业务解耦,会利用spring的机制,就是利用spring提供的ApplicationListener、ApplicationEventMulticaster等核心API来实现。(注: 我这边列的是核心底层API接口,正常我们会用监听事件用@EventListener,发布事件用 applicationContext.publishEvent()或者applicationEventPublisher.publishEvent())
本文案例主要来自团队的小伙伴,在利用spring事件机制踩到的坑。直接以案例的形式来讲解
示例案例
案例场景:当项目启动时,从数据库加载学生数据,并放到本地缓存。为了业务解耦 ,团队小王采用了spring的事件驱动方式来实现。他的实现步骤如下
1、定义学生事件
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、创建学生事件发布
@Service
@RequiredArgsConstructor
public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> implements StudentService,InitializingBean {private final StudentDao studentDao;private final ApplicationContext applicationContext;@Overridepublic void afterPropertiesSet() throws Exception {StudentEvent studentEvent = new StudentEvent(studentDao.listStudents());applicationContext.publishEvent(studentEvent);}@Overridepublic List<StudentEntity> listStudents() {return studentDao.listStudents();}
}
3、创建事件监听
@Component
public class StudentCache {private Map<Integer, StudentEntity> studentMap = new ConcurrentHashMap<>();@EventListenerpublic 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入手,通过源码我们可以发现事件监听的观察者,是通过
org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated
方法调用加入到事件监听容器,而这个方法的调用时机是在
org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
即在所有非懒加载单例bean加载入spring单例池后才触发调用。而案例中,事件的发布放在
com.github.lybgeek.student.service.impl.StudentServiceImpl#afterPropertiesSet
实现,该方法会比afterSingletonsInstantiated更先执行,而此时事件监听容器还没有该事件的观察者,就会导致事件发布了,但是没有相应观察者进行监听
问题修复
方法有很多种,可以利用spring自带的事件,比如监听ContextRefreshedEvent事件后,再进行事件发布
@EventListenerpublic void afterPropertiesSet(ContextRefreshedEvent contextRefreshedEvent) throws Exception {StudentEvent studentEvent = new StudentEvent(studentDao.listStudents());applicationContext.publishEvent(studentEvent);}
也可以利用spring其他扩展点,比如SmartInitializingSingleton,如果是springboot应用,还可以用CommandLineRunner或者ApplicationRunner
总结
本文修复问题的关键其实就是在于对spring一些扩展机制的优先调用顺序的了解