JavaFX cmd运行jar包报log4j.Logger类找不到NoClassDefFoundError错误
JavaFX应用在Eclipse、IntelliJ IDEA开发环境中运行正常,执行ant run指令启动也无异常,仅在Windows cmd命令行执行java -jar TheApp.jar命令启动时抛出错误,核心报错为java.lang.NoClassDefFoundError: org/apache/log4j/Logger,完整错误堆栈如下:
Exception in thread "JavaFX Application Thread" Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/Logger at com.th.app.ui.Login.<clinit>(Unknown Source) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Unknown Source) at com.sun.javafx.application.LauncherImpl.lambda$launchApplicationWithArgs$2(LauncherImpl.java:352) at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$7(PlatformImpl.java:326) at com.sun.javafx.application.PlatformImpl.lambda$null$5(PlatformImpl.java:295) at java.security.AccessController.doPrivileged(Native Method) at com.sun.javafx.application.PlatformImpl.lambda$runLater$6(PlatformImpl.java:294) at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95) at com.sun.glass.ui.win.WinApplication._runLoop(Native Method) at com.sun.glass.ui.win.WinApplication.lambda$null$4(WinApplication.java:185) at java.lang.Thread.run(Unknown Source) Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Logger at java.net.URLClassLoader.findClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) ... 12 more java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at sun.launcher.LauncherHelper$FXHelper.main(Unknown Source) Caused by: java.lang.NullPointerException at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:383) at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328) ... 5 more
初步怀疑是-classpath配置异常,耗时1天排查未解决。以下是可正常支持ant run运行的build.xml配置文件:
<?xml version="1.0"?> <project name="App" default="all" basedir="."> <property name="src" value="./src"/> <property name="build" value="./build"/> <property name="lib" value="./lib"/> <property name="dest" value="./dest"/> <property name="main-class" value="com.th.app.ui.Login"/> <path id="classpath"> <fileset dir="${lib}" includes="**/*.jar"/> </path> <target name="all" depends="clean, compile, jar, copy-file" description="Builds the whole project"> <echo>Doing all</echo> </target> <target name="clean" description="Removes previous build"> <delete verbose="true"> <fileset dir="${build}"/> </delete> </target> <target name="compile" depends="clean" description="compile whole project"> <echo>compile ${ant.project.name} </echo> <mkdir dir="${build}/classes"/> <copy file="./config.properties" tofile="${build}/classes/config.properties"/> <copy file="./src/log4j.properties" tofile="${build}/classes/log4j.properties"/> <copy todir="${build}/classes/com/th/app/ui"> <fileset dir="${src}/com/th/app/ui"> <include name="**/*.fxml"/> <include name="**/*.css"/> </fileset> </copy> <javac srcdir="${src}" destdir="${build}/classes" classpathref="classpath" includeantruntime="false" /> </target> <target name="jar" depends="compile"> <mkdir dir="${build}/jar"/> <jar destfile="${build}/jar/${ant.project.name}.jar" basedir="${build}/classes"> <manifest> <attribute name="Main-Class" value="${main-class}"/> </manifest> </jar> </target> <property name="args" value="READWRITE"/> <target name="run" depends="copy-file, input-runargs"> <java fork="true" classname="${main-class}"> <classpath> <path refid="classpath"/> <path location="${dest}/jar/${ant.project.name}.jar"/> </classpath> <arg line="${args}"/> </java> </target> <target name="input-runargs" unless="args" description="prompts for command line arguments if necessary"> <input addProperty="args" message="Type the desired command line arguments:"/> </target> <target name="copy-file"> <copy todir="${dest}"><fileset dir="${build}"/></copy> </target> </project>
该项目为提问者开发的第一个JavaFX项目,正处于交付阶段,需要明确的问题排查思路与可落地的解决方案。
报错核心原因是当前打包逻辑生成的应用jar,既没有内置第三方依赖,也没有在jar的清单文件中声明依赖的加载路径:
- IDE运行、
ant run启动正常,是因为这两种场景会自动把lib目录下的所有依赖包加入运行时类路径 - 用
java -jar指令启动时,JVM默认只会读取当前jar包内META-INF/MANIFEST.MF里声明的类路径,不会自动加载jar外部的第三方包,因此找不到log4j的类抛出NoClassDefFoundError,后续的空指针是JavaFX启动流程因类加载失败触发的连锁报错。
任选一种方案即可解决问题:
方案1:修改打包配置,在清单文件中声明依赖路径
修改build.xml中的jar目标,在manifest中增加Class-Path配置,指定依赖包的相对路径:<target name="jar" depends="compile"> <mkdir dir="${build}/jar"/> <!-- 拼接lib目录下所有依赖包的路径字符串 --> <pathconvert property="lib-classpath" pathsep=" "> <fileset dir="${lib}" includes="**/*.jar"/> <mapper type="flatten"/> </pathconvert> <jar destfile="${build}/jar/${ant.project.name}.jar" basedir="${build}/classes"> <manifest> <attribute name="Main-Class" value="${main-class}"/> <!-- 声明类路径,指向jar包同级lib目录下的所有依赖 --> <attribute name="Class-Path" value=". lib/${lib-classpath}"/> </manifest> </jar> </target>打包完成后,将项目的
lib目录复制到和生成的应用jar同级的位置,再执行java -jar TheApp.jar即可正常启动。方案2:打包含所有依赖的单jar包(fat jar)
如果不想额外携带lib目录,可以修改打包逻辑,把所有依赖的class文件都打进同一个应用jar,修改后的jar目标配置如下:<target name="jar" depends="compile"> <mkdir dir="${build}/jar"/> <jar destfile="${build}/jar/${ant.project.name}.jar" basedir="${build}/classes"> <manifest> <attribute name="Main-Class" value="${main-class}"/> </manifest> <!-- 将lib下所有jar的内容合并打进当前应用jar --> <zipgroupfileset dir="${lib}" includes="**/*.jar"/> </jar> </target>这种方式生成的单jar包不需要额外依赖文件,直接执行
java -jar指令即可启动。注意如果后续引入带签名的依赖包,需要在打包时排除签名文件,否则会触发安全校验报错,常规依赖如log4j、JavaFX组件不存在该问题。临时验证方案(无需修改打包配置)
如果只是临时测试启动,可以在cmd执行启动命令时手动指定全量类路径:java -cp "TheApp.jar;lib/*" com.th.app.ui.LoginWindows环境下类路径用分号分隔,Linux/macOS环境下用冒号分隔,路径要和实际存放jar、依赖的目录匹配。
内容的提问来源于stack exchange,提问作者Wai.Chan

