今天对一个工程代码做重构,这个工程原先是通过Spring的InitializingBean将自身注册到Zookeeper上,做的改造包括:
1)将注册中心的接口独立出来形成一个工程
2)基于注册中心基本接口扩展出Zookeeper实现
3)使用properties文件记录配置,取代原先Spring的XML配置
然后通过SPI机制实现自动注册,就可以脱离Spring容器使用了,但是在测试时,发现一直报错:
java.lang.NoClassDefFoundError : Could not initialize class org.apache.logging.log4j.util.Constants
很明显是log4j2出问题了,因为在第二处改造也引入了日志相关的依赖,自然而然就想到是不是日志冲突,但是整个项目所有位置都是用的同一个版本的log4j2,应该没有冲突。
项目中一共就四个依赖,采用排除法来确定问题范围:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>hnu.yhc.cn</groupId>
<artifactId>DDSSubscriber</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>XXX</groupId>
<artifactId>RDCSUtils</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>XXX</groupId>
<artifactId>ZkRobotServiceCenter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.12.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
</dependencies>
</project>
很明显log4j的两个依赖是不能去除的,那就是上面的两个依赖的问题,这两处正是这次改造引入的。ZkRobotServiceCenter有用到log4j,猜测问题出在这,排除后无效。于是试了下排除RDCSUtils,结果居然正常了。
奇怪的是,这个包没有引入任何其他依赖,并且目前只有一个类PropertyLoader用来加载配置文件,怎么会和日志系统有冲突呢?
这里贴一下代码:
public class PropertyLoader {
public static void load(String fileName){
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
Properties properties = new Properties();
try {
properties.load(inputStream);
System.setProperties(properties);
} catch (IOException e) {
e.printStackTrace();
}
}
}
作用也很简单,就是加载classpath下的properties文件而已,code review一下,立刻发现了问题,这里直接覆盖了System的Properties,再看一下错误抛出位置的代码:
public static final boolean IS_WEB_APP = PropertiesUtil.getProperties().getBooleanProperty(
"log4j2.is.webapp", isClassAvailable("javax.servlet.Servlet"));
果然是有读取Properties的操作,猜测是不是因为配置被覆盖导致错误,但是从代码里看,这里是不会抛出异常的,于是用单步调试来看,最终发现,问题实际是出在
public static final int JAVA_MAJOR_VERSION = getMajorVersion();
getMajorVersion方法如下:
private static int getMajorVersion() {
return getMajorVersion(System.getProperty("java.version"));
}
static int getMajorVersion(final String version) {
final String[] parts = version.split("-|\\.");
boolean isJEP223;
try {
final int token = Integer.parseInt(parts[0]);
isJEP223 = token != 1;
if (isJEP223) {
return token;
}
return Integer.parseInt(parts[1]);
} catch (final Exception ex) {
return 0;
}
}
可以看到,它在没有做任何保护的情况下就直接做了split操作,由于上面代码编写的疏忽,导致java.version这个配置值丢失,当然就触发了NPE
由于NPE发生在static块中,而IS_WEB_APP这个值是Constants类第一个被调用的,这时候就已经会把整个类所有static变量都初始化,并执行所有static块内容,所以在此处就已经抛出异常
实际上这时候抛出的错误是ExceptionInInitializerError,之后就是沿着调用链不断向上抛出,因为整条链上都是catch的Exception,所以没有被拦住,直达LogManager,导致其初始化失败,最终项目无法启动
没有搞清楚的是为什么最终堆栈显示的错误是NoClassDefFoundError,这个错误也比较有迷惑性,把思路带偏了