志在指尖
用双手敲打未来

Java静态变量的循环依赖

布景
是的,标题没有错误,不是SpringBean的循环依靠,而是静态变量之间的循环依靠。
近期的项目均是简略的Maven项目,经过K8S部署在阿里云上,其装备文件读取规矩如下所示:
(1)优先读取运用部署同层目录下的装备文件;
(2)假如没有外部装备文件,则读取打包至jar包中的装备文件。
在部署的过程中,发现运用无法争取读取外部装备文件中的装备信息,坚持不懈的读取打包文件中的。Java
日志文件装备错误
xmlversion=”1.0″encoding=”UTF-8″?><configurationscan=”true”scanPeriod=”60seconds”debug=”false”><contextName>external-portal-inspectorcontextName><propertyname=”LOG_PATH”value=”./logs”/><propertyresource=”application.properties”/><definename=”application.system”class=”com.eqos.network.config.LogSystemParamDefiner”/><definename=”application.region”class=”com.eqos.network.config.LogRegionParamDefiner”/><definename=”application.platform”class=”com.eqos.network.config.LogPlatformParamDefiner”/><propertyname=”log.pattern”value='{
“date”:”%date{\\\”yyyy-MM-ddHH:mm:ss.SSS\\\”,UTC}”,
“level”:”%level”,
“system”:”${application.system}”,
“platform”:”${application.platform}”,
“region”:”${application.region}”,
“filepath”:”%class:%line”,
“msg”:”%msg”
}’/><appendername=”console”class=”ch.qos.logback.core.ConsoleAppender”><filterclass=”ch.qos.logback.classic.filter.ThresholdFilter”><level>DEBUGlevel>filter><encoderclass=”net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder”><providersclass=”net.logstash.logback.composite.loggingevent.LoggingEventJsonProviders”><pattern><pattern>${log.pattern}pattern>pattern>providers><charset>UTF-8charset>encoder>appender>configuration>
截取部分日志装备,日志格式界说为json格式,其中system,level以及platform参数是在程序运行时进行获取的(经过logback.xml文件中界说的${application.system},${application.region},${application.platform},这几个参数后面界说了相关的类,运用在写日志时会调用相关类接口获取对应参数信息)。但是此时遇到问题,无论如何修正外部装备文件,日志均是读取Jar包中装备文件信息,在com.eqos.network.config.LogSystemParamDefiner类中打断点,发现程序未进入此类获取system信息。以下是application.properties文件中的装备信息。
application.platform=chongqing
application.region=HUADONG
application.system=network
生成日志信息如下所示:
{“date”:”2019-11-1401:48:42.695″,”level”:”INFO”,”system”:”network”,”platform”:”chongqing”,”region”:”HUADONG”,”filepath”:”com.eqos.network.service.TcpProbeService$1$1:78″,”msg”:”{\”tags\”:{\”namespace\”:\”oneNet.service.TCP.echo\”,\”metric.time-delay\”:24,\”metric.correct\”:true}}”}
简略对照一下装备文件,就会发现日志中运用${application.system},这与装备文件中的装备key值重名,会不会是日志程序在无法经过接口获取运用装备信息后,直接从Jar包内的装备文件中读取同名装备特点。针对此,修正logback.xml中的相关装备,如下所示:
<definename=”system”class=”com.eqos.network.config.LogSystemParamDefiner”/><definename=”region”class=”com.eqos.network.config.LogRegionParamDefiner”/><definename=”platform”class=”com.eqos.network.config.LogPlatformParamDefiner”/><propertyname=”log.pattern”value='{
“date”:”%date{\\\”yyyy-MM-ddHH:mm:ss.SSS\\\”,UTC}”,
“level”:”%level”,
“system”:”${system}”,
“platform”:”${platform}”,
“region”:”${region}”,
“filepath”:”%class:%line”,
“msg”:”%msg”
}’/>
修正如上所示,去除application前缀,重新发动程序:
{“date”:”2019-11-1401:54:24.486″,”level”:”INFO”,”system”:”system_IS_UNDEFINED”,”platform”:”platform_IS_UNDEFINED”,”region”:”region_IS_UNDEFINED”,”filepath”:”com.eqos.network.service.TcpProbeService$1$1:78″,”msg”:”{\”tags\”:{\”namespace\”:\”oneNet.service.TCP.echo\”,\”metric.time-delay\”:33,\”metric.correct\”:true}}”}
{“date”:”2019-11-1401:54:24.546″,”level”:”INFO”,”system”:”system_IS_UNDEFINED”,”platform”:”platform_IS_UNDEFINED”,”region”:”region_IS_UNDEFINED”,”filepath”:”com.eqos.network.service.UdpProbeService$1:87″,”msg”:”{\”tags\”:{\”namespace\”:\”oneNet.service.UDP.echo\”,\”metric.time-delay\”:5,\”metric.correct\”:true}}”}
美妙的工作发作了,去除了前缀后,日志运用就无法从Jar包内的装备文件读取同名的特点信息了,日志中的system显示为system_IS_UNDEFINED,此处带上了_IS_UNDEFINED后缀,标明日志运用无法获取此特点对应的详细数值。
进一步查看日志装备文件,发现从装备文件读取特点是由于装备了以下特点:。
后续的问题就集中于,为什么日志运用无法读取com.eqos.network.config.LogSystemParamDefiner经过接口提供的特点信息呢?
静态变量循环依靠
抽丝剥茧
以下为com.eqos.network.config.LogSystemParamDefiner的相关代码,其承继于logabck提供的PropertyDefinerBase抽象类,当运用发动时,logback就会调用实现此抽象类的目标,获取特点的详细数值。
publicclassLogSystemParamDefinerextendsPropertyDefinerBase{@OverridepublicStringgetPropertyValue(){returnAppConfig.INSTANCE.getConfigInfoMap().getOrDefault(“application.system”,”UNKNOWN”);
}
}@Slf4jpublicclassAppConfig{publicstaticfinalAppConfigINSTANCE=newAppConfig();privatefinalConcurrentHashMapconfigInfoMap=newConcurrentHashMap<>(16);privateAppConfig(){//读取装备文件中特点,相关代码非重点,因而省略initApplicationConfig();
}publicConcurrentHashMapgetConfigInfoMap(){returnconfigInfoMap;
}
}
由以上代码可以看出,LogSystemParamDefiner类较为简略,其依靠于AppConfig类的单例目标。AppConfig类也很简略,经过静态变量实现单例(当前运用程序就不考虑反射或许序列化破坏单例了)。那就只能打断点跟进,看看终究是什么原因导致无法读取system特点,按理说此处就算无法正确读取到system特点值,也会运用UNKNOWN值进行代替。
打断点进入程序,发现在发动阶段进入LogSystemParamDefiner.getPropertyValue办法,为难的工作发作了,此时AppConfig.INSTANCE为null(是的,java中的绝大多数问题都是NPE,此处就不计较为什么NPE被吞了)。这是为什么呢,按理说静态单例里应该在AppConfig类进行加载的时分,就创立相应的静态变量目标,不该该为空。
探寻终究
现在问题很简略,便是为什么AppConfig的单例类初始化失利呢?经过有限的编程经验来说,应该是发作循环依靠了,而且这个循环依靠必定是发作在日志单例和AppConfig单例之间(logback的实现肯定是单例,我想不到它不是单例的理由)。
查看AppConfig代码,其实主要便是查看import引进的依靠项,代码如下所示:
importlombok.extern.slf4j.Slf4j;importjava.io.File;importjava.io.FileInputStream;importjava.io.IOException;importjava.io.InputStream;importjava.util.Properties;importjava.util.concurrent.ConcurrentHashMap;
问题已很明显了,只有Slf4j与日志相关,应该便是它引进了对logback单例的依靠。查看AppConfig代码,发现尽管未运用到日志类,但是在类上存在@Slf4j注解。现在去除@Slf4j注解,再次进行试验发现,代码再次进入LogSystemParamDefiner.getPropertyValue办法时,AppConfig.INSTANCE已经完成了初始化操作,输出日志也正常加载外部装备文件的相关信息。
举个比如
classA{publicstaticintX;static{X=B.Y+1;}
}publicclassB{publicstaticintY=A.X+1;static{}publicstaticvoidmain(String[]args){
System.out.println(“X=”+A.X+”,Y=”+B.Y);
}
}
讲原理大家或许不太容易了解,此处就经过stackoverflow上的比如进行解说。这个代码假如写在实践的工程中,或许会被打的很惨(是的,前面的问题确实很愚蠢),不过很容易看出来类A与类B中的静态变量存在循环依靠。
以下解说此程序的履行次序:
(1)主线程履行main函数吗,classloader加载类B;
(2)加载类B时,初始化静态变量B.Y;
(3)B.Y依靠于A.X,因而classloader加载类A;
(4)加载类B时,初始化静态变量A.X,A.X依靠于B.Y;
(5)此时B尚未加载完全,因而B.Y数值为0(假如B.Y为Object,则对应数值为null),所以A.X数值为1;
(6)回来类B继续履行B.Y的初始化操作,由于A.X=1,因而B.Y数值为2;
(7)类A与类B的初始化完成,A.X=1,B.Y=2。
以上代码能够得到详细数值,这是由于java关于根本类型以及目标类型均会赋予初始化数值。假如换成C++,在UB的指引下,会出现什么那就真的不得而知了。

未经允许不得转载:IT技术网站 » Java静态变量的循环依赖
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

志在指尖 用双手敲打未来

登录/注册IT技术大全

热门IT技术

C#基础入门   SQL server数据库   系统SEO学习教程   WordPress小技巧   WordPress插件   脚本与源码下载