使用 Launch4J 构建的 Micronaut 应用程序未启动

Micronaut application built with Launch4J not launching

我已经成功地将我的桌面应用程序从 Dagger/Spring 移植到 Micronaut/Spring(那里的 Spring 代码太多了,我无法及时将其全部删除方式)。使用 Micronaut 和 PicoCLI,一切都在 Eclipse/Gradle 中完美运行。它正在启动,测试正在通过,所以现在我想构建并安装它以确保一切都适用于我们的客户。

我们的构建(通过 Gradle)使用 Shadow(com.github.johnrengelman.shadow)将我们的应用程序及其所有依赖项组合到一个 Jar 文件中。我已经检查过,所有预期的依赖项似乎都存在,从它的外观来看,生成的 Micronaut 类 也存在于 shadow jar 中。然后我们使用 Launch4J 使这个 shadow jar 可执行。据我所知,Launch4J 的输出看起来是正确的,但是当我尝试 运行 安装的应用程序时,启动画面只出现了几分之一秒,仅此而已。当我 运行 Launch4J 生成可执行文件作为 jar 文件时,我得到以下信息:

Error: An unexpected error occurred while trying to open file My App.exe

我们正在 运行 OpenJDK11 (11.0.2) 并生成一个 JRE 作为 gradle build

的一部分
runtime {
    modules = [
        'java.base',
        'java.compiler',
        'java.datatransfer',
        'java.desktop',
        'java.instrument',
        'java.logging',
        'java.management',
        'java.naming',
        'java.prefs',
        'java.rmi',
        'java.scripting',
        'java.security.sasl',
        'java.sql',
        'java.transaction.xa',
        'java.xml',
        'jdk.compiler',
        'jdk.httpserver',
        'jdk.javadoc',
        'jdk.naming.rmi',
        'jdk.unsupported'
    ]
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
}

我收集到的所有 Launch4J 调试信息看起来都是正确的(退出代码 1 除外),到目前为止,我对此进行的所有深入研究都被证明是徒劳的。我收集到此类错误的最常见原因是 jar 中的 MANIFEST.MD 无效(我已经检查过,它与原始的 Micronaut 之前的清单相同),或者类路径问题(shadow jar 应该包含所有必需的依赖项,据我所知没有使用其他类路径引用)。 build.gradle的相关部分如下:

task createExe(type: Launch4jLibraryTask) {
    jarTask = shadowJar
    icon = "${projectDir}/src/main/installer/nsis/Icon.ico"
    outfile = "${appName}.exe"
    copyConfigurable = files {}
    stayAlive = 'true'
    bundledJrePath = 'jre'
    bundledJre64Bit = true
    jvmOptions = [
            '-Xmx1024m',
            '-Dsun.java2d.d3d=false'
    ]
    fileDescription = appName + ' Application'
    productName = appName
    internalName = appName
    copyright = 'My Company'
    companyName = 'My Company'
}

jar {
    manifest {
        attributes(
                'Main-Class': mainClassName,
                'SplashScreen-Image': 'images/Z006BASSPS.jpg'
        )
    }
}

shadowJar {
    dependsOn replaceTokens
    zip64 true
    mergeServiceFiles() {
        exclude 'META-INF/services/org.xmlpull.v1.XmlPullParserFactory'
    }
    append 'META-INF/spring.factories'
    append 'META-INF/spring.handlers'
    append 'META-INF/spring.schemas'
    append 'META-INF/spring.tooling'
}

None 其中自 Micronaut 之前一切正常时已更改。

我已经尝试使用它生成的 JRE 启动由构建生成的影子 jar,看起来它正在工作(或者至少,它正在执行而不是立即死亡)。所以我相当有信心问题出在构建的 Launch4J 部分。

Micronaut 与 Launch4j 兼容吗?是否缺少某些东西,或者可以尝试查看可能发生的其他事情?我真的很困惑,一切 看起来 应该可以,但事实并非如此...

更新:

经过大量挖掘后,我开始意识到问题出在 Launch4j 上。此构建生成的影子罐运行良好。然而 运行通过 Launch4j 连接工作 Jar 会产生一个只生成

的可执行文件
Error: An unexpected error occurred while trying to open file launch4j\MyApp.exe

我创建了一个虚拟的 Micronaut 应用程序并使用 Launch4j 生成它的构建似乎也完全按预期工作,所以我认为这不是 Launch4j - Micronaut 不兼容的说法。

有什么方法可以从可执行文件中获取更多信息吗?实际可行的东西?我尝试将 headerType 从 gui 更改为控制台(根据我在此处看到的其他 Launch4j 问题),但这没有任何影响。我试过 运行 --l4j-debug(-all),并将其与升级前的输出进行比较,生成的日志文件中没有任何有意义的变化。

更新:

我相信我已经缩小了问题的范围:如果 jar 中的文件太多,launch4j 似乎无法工作。我假设限制是 65535,因为这是 Shadow 需要 zip64 标志的限制。在我的微型虚拟应用程序中,我将 70,000 个文本文件添加到资源目录中,并且在这些文件就位后,它将不再 运行(按照上面的描述)。一旦我将文本文件的数量减少到 50,000,一切又开始了 运行ning。我不确定每次说的限制是否为 65535(但这是我的期望),但一切似乎都指向它。

我还没有找到任何关于将 launch4j 用于超过 65535 个文件的信息(即:类似 shadowJar zip64 标志的东西),也没有找到关于这方面的一般信息。然而,一种对我有用的解决方案是将 dontWrapJar 设置为 true。这会为 运行 使用捆绑的 JRE 创建的 jar 文件创建一个微型启动器,而不是转换整个 jar,将所有(或至少大部分)文件保留在 launch4j 生成的可执行文件之外。更新后的gradle任务如下

task createExe(type: Launch4jLibraryTask) {
    jarTask = shadowJar
    dontWrapJar true // <<<< THIS IS THE ADDED LINE
    icon = "${projectDir}/src/main/installer/nsis/Icon.ico"
    outfile = "${appName}.exe"
    copyConfigurable = files {}
    stayAlive = 'true'
    bundledJrePath = 'jre'
    bundledJre64Bit = true
    jvmOptions = [
            '-Xmx1024m',
            '-Dsun.java2d.d3d=false'
    ]
    fileDescription = appName + ' Application'
    productName = appName
    internalName = appName
    copyright = 'My Company'
    companyName = 'My Company'
}

除此之外,唯一的其他更改是更新打包过程以将生成的影子 jar 合并到生成的安装程序的 lib 目录中。