今天對一個工程代碼做重構,這個工程原先是通過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,這個錯誤也比較有迷惑性,把思路帶偏了