記錄一個疏忽導致的異常

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章