【问题标题】:Java & Gradle & log4j NoClassDefFoundError: org/apache/logging/log4j/LogManagerJava & Gradle & log4j NoClassDefFoundError: org/apache/logging/log4j/LogManager
【发布时间】:2021-08-13 19:08:45
【问题描述】:

我正在尝试在我的 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 中使用。谁能解释一下为什么?

【问题讨论】:

  • 如果 JAR 文件需要外部依赖项,您需要使用 -cp 参数到 java 可执行文件(或 CLASSPATH 环境变量)。或者,一些 Java 实现可以通过读取清单(参见this question)向类路径添加额外的 JAR
  • @PiotrP.Karwasz,您能否分享有关此的任何文档?我已经阅读了 gradle 文档,但没有发现我们应该指定类路径等以使用 implementation
  • What is a classpath and how do I set it? 当 Intellij 运行一个项目时,它会做一些类似于设置类路径的事情。
  • @PiotrP.Karwasz,对不起,我在 Gradle 中找不到任何关于它的文档。我看到了链接,但是我应该告诉我的build.gradle 什么类路径?因为如果我输入implementation,在我构建的 .jar 中没有 log4j jar。只有当我输入compile。我已经读过它们在传递依赖关系中的区别,但可能还有别的东西。让我感到不安的是 - 没有关于它的文档。

标签: java gradle dependencies log4j log4j2


【解决方案1】:

当您启动应用程序时,您遇到的问题是由应用程序的类路径中的问题引起的(参见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

(我假设所有 jars 都在当前目录中;路径分隔符是系统特定的,在 Windows 上是 ;

这变得很容易难以解决,因此application Gradle 插件会生成一个 shell 和一个批处理脚本来帮助您:执行installDist task 并查看build/install/appName(或者您可以使用distZipdistTar 此文件夹的压缩版本的任务)。设置正确的命令简化为调用:

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 的使用,这是一个包含启动应用程序所需的runtimeOnlyimplementation 依赖配置的配置。

如果依赖 jar 与 appName.jar 位于同一文件夹中,则可以使用以下命令运行应用程序:

java -jar appName.jar

Spring Boot 加载器

如果您想在单个 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 文件和所有依赖项解压缩到一个文件夹中并将其重新压缩在一起来生成的。

这可行,但有一些缺点:例如您可能会丢失有关依赖项的许可证信息(这可能是违反许可证),并且您将无法在不重新编译的情况下用新版本替换依赖项。请参阅 this question 获取一个大得离谱的肥罐的示例。

【讨论】:

    猜你喜欢
    • 2018-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多