记录一个疏忽导致的异常

今天对一个工程代码做重构,这个工程原先是通过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,这个错误也比较有迷惑性,把思路带偏了

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