一、case复现
事件定义
public class MyEvent extends ApplicationEvent {
public MyEvent(Object object) {
super(object);
}
}
监听定义
@Component
public class MyListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
System.out.println("myEvent occured msg : " + event.getSource());
}
}
事件通知
@ResponseBody
@RequestMapping(value = "/publish")
public String publish(String key) {
BeanFactory.pushEvent(new MyEvent("publish"));
return "success";
}
@Component
public class BeanFactory implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static <T> T getBean(String beanName, Class<T> clazz) {
return (T) applicationContext.getBean(beanName);
}
/**
* 通知事件
*
* @param applicationEvent
*/
public static void pushEvent(ApplicationEvent applicationEvent) {
//获取父容器发送事件
//ContextLoader.getCurrentWebApplicationContext().publishEvent(applicationEvent);
applicationContext.publishEvent(applicationEvent);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
}
测试结果:
myEvent occured msg : publish
myEvent occured msg : publish
三、原因
在web项目中如果同时集成了spring和springMVC的话,上下文中会存在两个容器,即spring的applicationContext.xml的父容器和springMVC的applicationContext-mvc.xml的子容器。
在通过applicationContext发送通知的时候,事件会被两个容器发布,造成上述情况。
四、解决方案
知道了原因,解决方案就比较简单了,看了网上大多数的方案都是
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if(event.getApplicationContext().getParent() == null){
//需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
}
}
但这种方案先定了事件的类型,自定义的事件是行不通的,所以解决方案的思路是用父容器去发送通知
/**
* 通知事件
*
* @param applicationEvent
*/
public static void pushEvent(ApplicationEvent applicationEvent) {
//获取父容器发送事件
ContextLoader.getCurrentWebApplicationContext().publishEvent(applicationEvent);
}
测试结果:
myEvent occured msg : publish
至此,解决了这个case的问题,多大家有更好更多的方案,希望留言一起学习