context has been closed already 解决方案
报错代码
有这样一段代码,在运行中可能会发生 context has been closed already
错误,且一旦发生,以后每次运行到这必定出错。
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext context = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextExt.context = applicationContext;
}
/**
* 可以通过该静态方法从 Spring 上下文中获取想要的 Bean。
*/
public static <T> T getBean(Class<T> cls) {
if (context == null)
throw new IllegalStateException("no application context aviliable.");
try {
return (T) context.getBean(cls); // 这里报错!!!
} catch (BeansException e) {
throw new RuntimeException(e);
}
return (T) null;
}
}
原因分析
但是类路径中包含 spring-cloud-context.jar
时,因为 ContextRefresher
这个类会在上下文刷新时关闭旧的上下文,导致这个静态类里的上下文是已经关闭的,因此会产生这个错误。
触发探索
省略debug过程,直接说原因,发现架构组设计了个钩子,当收到中央服务发送的【插件安装广播】时,会通过 refresh Spring 上下文以重新加载配置,以达到运行时安装插件的效果。
以下是代码示例:
public class MsgConcumer {
private static ApplicationContext context = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextExt.context = applicationContext;
}
/**
* 坑爹的二方包,自己设计了个钩子,当收到中央服务发送的【插件安装广播】时,会通过 refresh Spring 上下文以重新加载配置。
*/
public void onMessage(Message msg) {
if("reload_config".equlas(msg.getType()) {
context.refresh();
}
}
}
解决
- 修改设计方案,手动写代码加载,而不是这样偷工减料。
- 【不推荐】对使用jar包的人提要求,如果使用了该jar包,所有 setApplicationContext() 的地方都要再去监听 ContextedRefresh 通知重新设置 context。