Java & Gradle & log4j NoClassDefFoundError: org/apache/logging/log4j/LogManager
Java & Gradle & log4j NoClassDefFoundError: org/apache/logging/log4j/LogManager
我正在尝试在我的 java 核心控制台应用程序中使用 log4j。在文档中有关于如何为 log4j 添加 Gradle 依赖项的示例代码:
https://logging.apache.org/log4j/2.x/maven-artifacts.html
dependencies {
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.14.1'
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.14.1'
}
我将其粘贴到我的 build.gradle
文件中并为 运行 编写了示例代码。
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Program {
private static final Logger logger = LogManager.getLogger("HelloWorld");
public static void main(String[] args) {
logger.error("Just a test error entry");
}
}
除此之外,我还添加了清单,以便 运行 来自控制台的代码。
jar {
manifest {
attributes "Main-Class": "Program"
}
}
如果我使用 Gradle 构建它 - 一切都会成功构建。但是如果我尝试 运行 java -jar 从我的构建目录然后我得到
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/logging/log4j/LogManager
at Program.<clinit>(Program.java:5)
Caused by: java.lang.ClassNotFoundException: org.apache.logging.log4j.LogManager
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:636)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:182)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:519)
... 1 more
我找到了 1 个解决方法:修改 jar{} 部分:
jar {
manifest {
attributes "Main-Class": "Program"
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
它开始工作了,我什至尝试压缩 .jar 文件并看到添加了 log4j 依赖项。
但是我不想传递对 log4j 的依赖是什么?如果我设置 implementation group
而不是 compile group
它就不会再起作用了。有没有完整的使用log4j的例子?
最重要的是,要指出一件事,虽然它不能与 java -jar <.jar> 一起使用,但出于某种原因它仍然可以在 Intellij idea 中使用。有人可以解释一下为什么吗?
您遇到的问题是由启动应用程序时应用程序类路径中的问题引起的(参见 What is a classpath and how do I set it?)。
Gradle 有很多选项可以帮助您正确设置类路径。由于您没有提及任何 Gradle 版本,我将在示例中使用版本 7.1。假设您在名为 appName
:
的项目中有此 build.gradle
文件
plugins {
id 'application'
}
dependencies {
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.14.1'
runtimeOnly group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.14.1'
}
application {
mainClass = 'com.example.App'
}
(log4j-core
不是编译你的代码所必需的,所以它在 runtimeOnly
依赖配置中,参见 dependency configurations)
手动设置类路径
如果生成普通 JAR 文件(执行 jar
task 并查看 build/libs
),您可以使用以下命令执行应用程序:
java -cp log4j-api-2.14.1.jar:log4j-core-2.14.1.jar:appName.jar com.example.App
(我假设所有 jar 都在当前目录中;路径分隔符是系统特定的,在 Windows 上是 ;
)
这变得很容易很难正确,因此 application
Gradle 插件生成一个 shell 和一个批处理脚本来帮助您:执行 installDist
task 并查看进入 build/install/appName
(或者您可以使用 distZip
或 distTar
任务来压缩此文件夹的版本)。设置正确的命令简化为调用:
bin/appName
在清单中硬编码类路径
您还可以在 JAR 清单文件中对所需的依赖项进行硬编码。在 Gradle 中,您可以使用:
jar {
manifest {
attributes(
'Main-Class': 'com.example.App',
'Class-Path': configurations.runtimeClasspath.collect { it.name }.join(' ')
)
}
}
获取包含启动应用程序所需的所有信息的清单文件:
Main-Class: pl.copernik.gradle.App
Class-Path: log4j-core-2.14.1.jar log4j-api-2.14.1.jar
注意 runtimeClasspath
的用法,这是一个包含启动应用程序所需的 runtimeOnly
和 implementation
依赖项配置的配置。
如果依赖 jar 与 appName.jar
在同一文件夹中,您可以 运行 应用程序:
java -jar appName.jar
Spring 引导加载程序
如果你想将所有依赖项都放在一个 JAR 中,你可以使用 Spring Boot Gradle plugin 而不是 application
插件:
plugins {
id 'java'
id 'org.springframework.boot' version '2.5.3'
}
bootJar
任务将生成一个 appName.jar
,其结构类似于 distZip
文件生成的 appName.zip
文件。然而 shell/batch 脚本被替换为 Java 代码,因此您可以调用:
java -jar appName.jar
胖罐
你在问题中做了什么:
jar {
manifest {
attributes "Main-Class": "com.example.App"
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
通常称为fat JAR。基本上它是通过将您的 jar 文件和所有依赖项解压缩到一个文件夹中并将其重新压缩在一起来生成的。
这可行,但有一些缺点:例如您可能会丢失有关依赖项的许可信息(这可能违反许可),并且您将无法在不重新编译的情况下用新版本替换依赖项。请参阅 一个大得离谱的胖罐子的例子。
我正在尝试在我的 java 核心控制台应用程序中使用 log4j。在文档中有关于如何为 log4j 添加 Gradle 依赖项的示例代码:
https://logging.apache.org/log4j/2.x/maven-artifacts.html
dependencies {
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.14.1'
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.14.1'
}
我将其粘贴到我的 build.gradle
文件中并为 运行 编写了示例代码。
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Program {
private static final Logger logger = LogManager.getLogger("HelloWorld");
public static void main(String[] args) {
logger.error("Just a test error entry");
}
}
除此之外,我还添加了清单,以便 运行 来自控制台的代码。
jar {
manifest {
attributes "Main-Class": "Program"
}
}
如果我使用 Gradle 构建它 - 一切都会成功构建。但是如果我尝试 运行 java -jar 从我的构建目录然后我得到
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/logging/log4j/LogManager
at Program.<clinit>(Program.java:5)
Caused by: java.lang.ClassNotFoundException: org.apache.logging.log4j.LogManager
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:636)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:182)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:519)
... 1 more
我找到了 1 个解决方法:修改 jar{} 部分:
jar {
manifest {
attributes "Main-Class": "Program"
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
它开始工作了,我什至尝试压缩 .jar 文件并看到添加了 log4j 依赖项。
但是我不想传递对 log4j 的依赖是什么?如果我设置 implementation group
而不是 compile group
它就不会再起作用了。有没有完整的使用log4j的例子?
最重要的是,要指出一件事,虽然它不能与 java -jar <.jar> 一起使用,但出于某种原因它仍然可以在 Intellij idea 中使用。有人可以解释一下为什么吗?
您遇到的问题是由启动应用程序时应用程序类路径中的问题引起的(参见 What is a classpath and how do I set it?)。
Gradle 有很多选项可以帮助您正确设置类路径。由于您没有提及任何 Gradle 版本,我将在示例中使用版本 7.1。假设您在名为 appName
:
build.gradle
文件
plugins {
id 'application'
}
dependencies {
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.14.1'
runtimeOnly group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.14.1'
}
application {
mainClass = 'com.example.App'
}
(log4j-core
不是编译你的代码所必需的,所以它在 runtimeOnly
依赖配置中,参见 dependency configurations)
手动设置类路径
如果生成普通 JAR 文件(执行 jar
task 并查看 build/libs
),您可以使用以下命令执行应用程序:
java -cp log4j-api-2.14.1.jar:log4j-core-2.14.1.jar:appName.jar com.example.App
(我假设所有 jar 都在当前目录中;路径分隔符是系统特定的,在 Windows 上是 ;
)
这变得很容易很难正确,因此 application
Gradle 插件生成一个 shell 和一个批处理脚本来帮助您:执行 installDist
task 并查看进入 build/install/appName
(或者您可以使用 distZip
或 distTar
任务来压缩此文件夹的版本)。设置正确的命令简化为调用:
bin/appName
在清单中硬编码类路径
您还可以在 JAR 清单文件中对所需的依赖项进行硬编码。在 Gradle 中,您可以使用:
jar {
manifest {
attributes(
'Main-Class': 'com.example.App',
'Class-Path': configurations.runtimeClasspath.collect { it.name }.join(' ')
)
}
}
获取包含启动应用程序所需的所有信息的清单文件:
Main-Class: pl.copernik.gradle.App
Class-Path: log4j-core-2.14.1.jar log4j-api-2.14.1.jar
注意 runtimeClasspath
的用法,这是一个包含启动应用程序所需的 runtimeOnly
和 implementation
依赖项配置的配置。
如果依赖 jar 与 appName.jar
在同一文件夹中,您可以 运行 应用程序:
java -jar appName.jar
Spring 引导加载程序
如果你想将所有依赖项都放在一个 JAR 中,你可以使用 Spring Boot Gradle plugin 而不是 application
插件:
plugins {
id 'java'
id 'org.springframework.boot' version '2.5.3'
}
bootJar
任务将生成一个 appName.jar
,其结构类似于 distZip
文件生成的 appName.zip
文件。然而 shell/batch 脚本被替换为 Java 代码,因此您可以调用:
java -jar appName.jar
胖罐
你在问题中做了什么:
jar {
manifest {
attributes "Main-Class": "com.example.App"
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
通常称为fat JAR。基本上它是通过将您的 jar 文件和所有依赖项解压缩到一个文件夹中并将其重新压缩在一起来生成的。
这可行,但有一些缺点:例如您可能会丢失有关依赖项的许可信息(这可能违反许可),并且您将无法在不重新编译的情况下用新版本替换依赖项。请参阅