前言
在日常業(yè)務(wù)開發(fā)中過程井氢,我們有時(shí)候?yàn)榱藰I(yè)務(wù)解耦瘪板,會(huì)利用spring的機(jī)制捂龄,就是利用spring提供的ApplicationListener淑仆、ApplicationEventMulticaster等核心API來實(shí)現(xiàn)涝婉。(注: 我這邊列的是核心底層API接口,正常我們會(huì)用監(jiān)聽事件用@EventListener蔗怠,發(fā)布事件用 applicationContext.publishEvent()或者applicationEventPublisher.publishEvent())
本文案例主要來自團(tuán)隊(duì)的小伙伴墩弯,在利用spring事件機(jī)制踩到的坑。直接以案例的形式來講解
示例案例
案例場景:當(dāng)項(xiàng)目啟動(dòng)時(shí)寞射,從數(shù)據(jù)庫加載學(xué)生數(shù)據(jù)渔工,并放到本地緩存。為了業(yè)務(wù)解耦 桥温,團(tuán)隊(duì)小王采用了spring的事件驅(qū)動(dòng)方式來實(shí)現(xiàn)引矩。他的實(shí)現(xiàn)步驟如下
1、定義學(xué)生事件
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侵浸、創(chuàng)建學(xué)生事件發(fā)布
@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旺韭、創(chuàng)建事件監(jiān)聽
@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能否正常接收到學(xué)生事件?
答案: 接收不到
問題解惑
首先我們要先確認(rèn)事件監(jiān)聽的觀察者掏觉,是何時(shí)加入事件監(jiān)聽容器区端?
我們可以從事件監(jiān)聽注解@EventListener入手,通過源碼我們可以發(fā)現(xiàn)事件監(jiān)聽的觀察者澳腹,是通過
org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated
方法調(diào)用加入到事件監(jiān)聽容器珊燎,而這個(gè)方法的調(diào)用時(shí)機(jī)是在
org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
即在所有非懶加載單例bean加載入spring單例池后才觸發(fā)調(diào)用。而案例中遵湖,事件的發(fā)布放在
com.github.lybgeek.student.service.impl.StudentServiceImpl#afterPropertiesSet
實(shí)現(xiàn)悔政,該方法會(huì)比afterSingletonsInstantiated更先執(zhí)行,而此時(shí)事件監(jiān)聽容器還沒有該事件的觀察者延旧,就會(huì)導(dǎo)致事件發(fā)布了谋国,但是沒有相應(yīng)觀察者進(jìn)行監(jiān)聽
問題修復(fù)
方法有很多種,可以利用spring自帶的事件迁沫,比如監(jiān)聽ContextRefreshedEvent事件后芦瘾,再進(jìn)行事件發(fā)布
@EventListener
public void afterPropertiesSet(ContextRefreshedEvent contextRefreshedEvent) throws Exception {
StudentEvent studentEvent = new StudentEvent(studentDao.listStudents());
applicationContext.publishEvent(studentEvent);
}
也可以利用spring其他擴(kuò)展點(diǎn),比如SmartInitializingSingleton集畅,如果是springboot應(yīng)用近弟,還可以用CommandLineRunner或者ApplicationRunner
總結(jié)
本文修復(fù)問題的關(guān)鍵其實(shí)就是在于對(duì)spring一些擴(kuò)展機(jī)制的優(yōu)先調(diào)用順序的了解