1. 问题背景
某应用在启动完提供JSF服务后,短时间内出现了大量的空指针异常。
分析日志,发现是服务依赖的藏经阁配置数据未加载完成导致。即所谓的有损上线或者是直接发布,当应用启动时,service还没加载完,就开始对外提供服务,导致失败调用。
关键代码如下
数据的初始化加载是通过实现CommandLineRunner接口完成的
@Component
public class LoadSystemArgsListener implements CommandLineRunner {@Resourceprivate CacheLoader cjgConfigCacheLoader;@Overridepublic void run(String... args) {// 加载藏经阁配置cjgConfigCacheLoader.refresh();}
}
cjgConfigCacheLoader.refresh()方法内部会将数据加载到内存中
/** 藏经阁配置数据 key:租户 value:配置数据 */
public static Map<String, CjgRuleConfig> cjgRuleConfigMap = new HashMap<>();
如果此时还未加载完数据,调用cjgRuleConfigMap.get("301").getXX(),则会报空指针异常
总结根因:JSF Provider发布早于服务依赖的初始化数据加载,导致失败调用
2. 问题解决
在解决此问题前,我们需要先回忆并熟悉下Spring Boot的启动过程、JSF服务的发布过程
1)Spring Boot的启动过程(版本2.0.7.RELEASE)
run方法,主要关注refreshContext(context)刷新上下文
public ConfigurableApplicationContext run(String... args) {// 创建 StopWatch 实例:用于计算启动时间StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();configureHeadlessProperty();// 获取SpringApplicationRunListeners:这些监听器会在启动过程的各个阶段发送对应的事件SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 创建并配置Environment:包括准备好对应的`Environment`,以及将`application.properties`或`application.yml`中的配置项加载到`Environment`中ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);configureIgnoreBeanInfo(environment);// 打印Banner:如果 spring.main.banner-mode 不为 off,则打印 bannerBanner printedBanner = printBanner(environment