应用程序开始提供 JSF 服务后,短时间内会出现大量空指针异常。
分析日志后发现,服务所依赖的藏经阁的配置数据没有加载。 所谓的有损上线或者是的直接出版当应用程序启动时,服务在加载之前就开始向外界提供服务,导致调用失败
密钥 ** 如下。
数据的初始加载是通过实现 commandlinerunner 接口完成的。
@componentpublic class loadsystemargslistener implements commandlinerunner }
cjgconfigcacheloader.refresh() 方法在内部将数据加载到内存中。
** Scripture Pavilion 配置数据 key: tenant value: 配置数据 * public static map cjgruleconfigmap = new hashmap<>(
如果数据尚未加载,请调用 cjgruleconfigmapget("301").getxx(),则会报空指针异常。
总结根本原因:jsf 提供程序在服务依赖关系之前发布初始数据加载,从而导致调用失败
在解决这个问题之前,我们需要回顾并熟悉一下Spring Boot的启动过程和JSF服务的发布过程。
运行方法,主要关注点refreshcontext(context) 中。
public configurableapplicationcontext run(string...args) ,context);准备上下文:主要将environment、applicationarguments等关键属性设置到applicationcontext中,并加载applicationlistener、applicationrunner、commandlinerunner等。 preparecontext(context, environment, listeners, applicationarguments, printedbanner);Refresh Context:这是启动 Spring IOC 容器的关键,包括 bean 创建、依赖注入、初始化、发布事件等。 afterrefresh(context, applicationarguments); stopwatch.stop();打印启动信息:如果弹簧main.log-startup-info 为 true,则打印 (this.) 的启动信息LogStartUpInfo):通知所有 SpringApplicationRunListeners 应用程序 Listeners 已启动started(context);调用 Runner:调用所有 ApplicationRunner 和 CommandLineRunner callrunners(context, applicationarguments); catch (throwable ex) try catch (throwable ex) return context;}
refreshcontext(context) 中。refresh() 方法,这是该方法的主要关注点FinishBeanFactoryInitialization(BeanFactory) 在 finishRefresh() 之前实例化 BEAN。
public void refresh() throws beansexception, illegalstateexception .
要实例化 Bean,您需要熟悉 Bean 生命周期(重要)。
com 类jd.jsf.gd.config.spring.providerbean 调用方法 comjd.jsf.gd.config.providerconfig 导出。
JSF源码地址:
public class providerbean extends providerconfig implements initializingbean, disposablebean, applicationcontextaware, applicationlistener, beannameaware after spring context refreshed.", this.beanname); if (this.delay < 1) catch (throwable var2) providerbean.this.export();thread.setdaemon(true); thread.setname("delayexportthread"); thread.start();else }private boolean isdelay() public void afterpropertiesset() throws exception after properties set.", this.beanname); this.export();
public synchronized void export() throws initerrorexception catch (throwable var2) providerconfig.this.doexport();thread.setdaemon(true); thread.setname("delayexportthread"); thread.start();else }
可以看出,提供者发布的地方有两个。
Bean 初始化过程 (delay>=0)。
实现 initializingbean 接口并重写 afterpropertiesset 方法。 如果它大于或等于 0,它将在此处发布。 具体来说,在导出方法中,如果延迟>0,则发布会延迟,例如,如果配置了 5000,则发布会延迟 5 秒如果 delay=0,则立即发布。
侦听要触发的 contextRefreshedEvent 事件 (delay<0)。
实现 ApplicationListener API 并重写 OnApplicationEvent 方法。 延迟时属于事件 contextRefreshedEvent
从上面的介绍中,我了解到执行顺序1.Bean 初始化> 2ContextRefreshedEvent 事件触发器 > 3调用 applicationrunner 或 commandlinerunner;
上面已经知道了提供程序发布正在进行中,请避免使用方法 3 初始化数据。
先决条件:delay 的默认值为 -1,可以保留为未配置或负数。 jsf 提供程序版本处于进程 2 中,该进程由侦听 contextRefreshedEvent 事件触发
@componentpublic class dataloader ") public void loaddata()
注意:如果 Bean 依赖于其他 Bean,请确保实例化依赖 Bean,否则会报告空指针异常。
contextRefreshedEvent 事件的发布方式。
调用过程 AbstractApplicationContext FinishRefresh ->AbstractApplicationContext PublishEvent-> ApplicationEventMulticaster MulticastEvent
public void multicastevent(final applicationevent event, @nullable resolvabletype eventtype) else }
在ApplicationEventMulticaster 的 MulticastEvent 方法调用 InvokeListener() 来发布事件,GetTaskExecutor() 默认为 null(Executor 对象的自定义设置除外),并且该类的所有 ApplicationListener 实现都串行执行 OnApplicationEvent 方法。
getApplicationListeners(event, type) 获取所有实现类,并继续查看内部调用 annotationawareordercomparatorsort(alllisteners) 对所有 applicationlisteners 进行排序,alllisteners 是要排序的对象列表。 此方法根据对象上的排序批注或接口确定排序顺序,并返回按指定顺序排序的对象列表。 具体来说,排序规则如下:
1.首先,根据对象上@order注释的值进行排序。 @order 注解值越小,排序优先级越高
2.如果对象上没有@order注释,或者多个对象具有相同的@order注释值,则顺序基于对象是否实现有序接口。 实现有序接口的对象可以通过 getorder() 方法返回排序值。
3.如果对象既没有@order注释,也没有有序接口,则使用默认排序值“最低优先级(整数)”max_value)。特殊:如果 beana 和 beanb 排序值都是默认值,则保持原始顺序,即 bean 的加载顺序
摘要:默认该类的所有 ApplicationListener 实现都按顺序执行 OnApplicationEvent 方法,顺序取决于 AnnotationAwareOrderComparatorsort(alllisteners),@order注解的值越小,排序优先级越高
@component@order(1)public class dataloader implements applicationlistener }
也可以在具有 @springbootapplication 的启动类中实现它(默认情况下,在 Spring Boot 中,使用基于注解的配置和管理 bean,因此在 XML 定义的 bean 之前加载注解定义的 bean)。
@springbootapplicationpublic class demoapplication implements applicationlistener @override public void onapplicationevent(contextrefreshedevent event) }
应用程序启动后,初始化应用程序,然后手动发布提供程序。 这可以通过实现接口 applicationrunner 或接口 commandlinerunner 来执行初始化来完成。
@componentpublic class dataloader implements applicationrunner }
提供程序的 dynamic 属性设置为 false
RPC 服务(如 JSF 和 DUBBO)通常以两种方式启动: 1. 延迟发布 2.手动启动
如果您的服务需要一些初始化操作才能提供外部服务,例如初始化缓存(不限于采集经文、DUCC、MySQL,甚至调用其他 JSF 服务)、Redis 连接池等相关资源到位,可以参考本文介绍的方法。
本文是作者通过阅读源码+本地验证得出的结论,如果有任何错误或遗漏或更好的解决方案,请指出共同的进展!