AWS Lambda 使用多版本 JAR 中的错​​误类文件?

And*_*ert 1 java log4j2 aws-lambda multi-release-jar

我的 lambda 在 Java8 下运行了几年,我刚刚将其更新到 Java 11。它立即崩溃了,给了我这样的错误:

\n
Caused by: java.lang.ExceptionInInitializerError\n    at com.mycompany.rest.providers.JsonProvider.writeTo(JsonProvider.java:80)\n    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:242)\n    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:227)\n    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:139)\n    at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1116)\n    at org.glassfish.jersey.client.ClientRequest.doWriteEntity(ClientRequest.java:461)\n    at org.glassfish.jersey.client.ClientRequest.writeEntity(ClientRequest.java:443)\n    at org.glassfish.jersey.client.internal.HttpUrlConnector._apply(HttpUrlConnector.java:367)\n    at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:265)\n    at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:297)\n    ... 15 more\nCaused by: java.lang.UnsupportedOperationException: No class provided, and an appropriate one cannot be found.\n    at org.apache.logging.log4j.LogManager.callerClass(LogManager.java:571)\n    at org.apache.logging.log4j.LogManager.getLogger(LogManager.java:596)\n    at org.apache.logging.log4j.LogManager.getLogger(LogManager.java:583)\n    at com.mycompany.rest.util.NonClosingOutputStream.<clinit>(NonClosingOutputStream.java:11)\n    ... 25 more\n
Run Code Online (Sandbox Code Playgroud)\n

所讨论的类并不是特别令人兴奋,并且具有在我的类中常见的简单静态初始化:

\n
public class NonClosingOutputStream extends ProxyOutputStream {\n    private static final Logger log = LogManager.getLogger(); // Line 11\n\n    public NonClosingOutputStream(final OutputStream proxy) {\n        super(proxy);\n    }\n\n    ...\n
Run Code Online (Sandbox Code Playgroud)\n

当我将我的(非 Lambda)java 服务器从 8 切换到 11 时,我以前见过这样的问题;我需要将我的 jar 清单标记为Multi-Release: true,因为我依赖的 ApacheLog4j 工件为org.apache.logging.log4j.util.StackLocatorJava 8 和 9+ 中的类提供了替代实现。然而,我希望 JVM 能够选择该类的适当版本。我必须在某处设置一些配置吗?是否有可能将我的 Lambda 从 Java 8 切换到 Java 11感到困惑某些东西?

\n

jar/META-INF/versions:

\n
versions/\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 11\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 org\n\xe2\x94\x82\xc2\xa0\xc2\xa0     \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 glassfish\n\xe2\x94\x82\xc2\xa0\xc2\xa0         \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 jersey\n\xe2\x94\x82\xc2\xa0\xc2\xa0             \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 internal\n\xe2\x94\x82\xc2\xa0\xc2\xa0                 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 jsr166\n\xe2\x94\x82\xc2\xa0\xc2\xa0                     \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 JerseyFlowSubscriber$1.class\n\xe2\x94\x82\xc2\xa0\xc2\xa0                     \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 JerseyFlowSubscriber.class\n\xe2\x94\x82\xc2\xa0\xc2\xa0                     \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 SubmissionPublisher$1.class\n\xe2\x94\x82\xc2\xa0\xc2\xa0                     \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 SubmissionPublisher$2.class\n\xe2\x94\x82\xc2\xa0\xc2\xa0                     \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 SubmissionPublisher$3.class\n\xe2\x94\x82\xc2\xa0\xc2\xa0                     \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 SubmissionPublisher$4.class\n\xe2\x94\x82\xc2\xa0\xc2\xa0                     \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 SubmissionPublisher$5.class\n\xe2\x94\x82\xc2\xa0\xc2\xa0                     \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 SubmissionPublisher$6.class\n\xe2\x94\x82\xc2\xa0\xc2\xa0                     \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 SubmissionPublisher.class\n\xe2\x94\x82\xc2\xa0\xc2\xa0                     \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 SubmissionPublisherFactory.class\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 9\n    \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 module-info.class\n    \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 org\n        \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 apache\n            \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 logging\n                \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 log4j\n                    \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 core\n                    \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 util\n                    \xe2\x94\x82\xc2\xa0\xc2\xa0     \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 SystemClock.class\n                    \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 util\n                        \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 Base64Util.class\n                        \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 ProcessIdUtil.class\n                        \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 StackLocator.class\n                        \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 internal\n                            \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 DefaultObjectInputFilter.class\n
Run Code Online (Sandbox Code Playgroud)\n

编辑:我正在寻找一些参考资料表明,当 AWS Lambda 提取 JAR 时,它们不会提取 META-INF 目录,该目录包含 MANIFEST.MF 文件,该文件告诉 JVM 该 JAR 是多版本 JAR。Lambda 是否支持多版本 JAR?

\n

Maa*_*rak 5

不完全是您问题的答案,但我希望这可能有所帮助。

您的分析是正确的 - AWS lambda 提取了整个 JAR 文件。然后,运行 lambda 函数的 JVM 不再将代码识别为 JAR 文件,并且实际上整个 META-INF 目录都会被忽略。

就我而言,我使用maven-shade-plugin创建一个“uber”-jar,其中包含 lambda 函数的所有依赖项。AWS官方文档中推荐了这种方法。现在 - 这很重要 -maven-shade-plugin提取所有 jar 文件依赖项并将它们重新打包到单个平面 jar 文件中。如果您的依赖项之一是多版本 jar(如 log4j2),那么您可以配置maven-shade-plugin以重建适当的 META-INF 目录,并且如果您将 jar作为 jar 文件运行,则一切仍然有效。但由于 AWS Lambda 提取了 jar,因此 JVM 不再“看到” META-INF 目录,并且 META-INF/versions 中的任何内容都将被忽略。

为了解决这个问题,我切换到了maven-assembly-plugin. 它允许使用 lambda 代码创建 ZIP 文件,并将依赖项添加为 JAR 文件。现在,当 AWS Lambda 提取此 ZIP 文件时,JAR 保持完整并且一切正常。

assembly.xml要配置它,请创建一个如下文件:

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
    <id>zip</id>
    <!-- Make sure the ZIP contents are not nested in a subdirectory -->
    <includeBaseDirectory>false</includeBaseDirectory>

    <formats>
        <format>zip</format>
    </formats>
    <fileSets>
        <fileSet>
            <directory>${project.basedir}/conf</directory>
        </fileSet>
        <!-- Include the compiled classes as-is and put them in the root of the ZIP -->
        <fileSet>
            <directory>${project.build.directory}/classes</directory>
            <outputDirectory>/</outputDirectory>
        </fileSet>
    </fileSets>
    <dependencySets>
        <!-- Include all dependencies in the lib/ directory -->
        <dependencySet>
            <outputDirectory>lib</outputDirectory>
            <excludes>
                <exclude>${project.groupId}:${project.artifactId}:jar:*</exclude>
            </excludes>
        </dependencySet>
    </dependencySets>
</assembly>
Run Code Online (Sandbox Code Playgroud)

然后你需要maven-assembly-plugin在你的pom.xml

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>3.3.0</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
            <configuration>
                <appendAssemblyId>false</appendAssemblyId>
                <descriptors>
                    <descriptor>assembly.xml</descriptor>
                </descriptors>
                <finalName>${project.artifactId}</finalName>
            </configuration>
        </execution>
    </executions>
</plugin>
Run Code Online (Sandbox Code Playgroud)

现在只需像往常一样将生成的 zip 文件部署到 AWS Lambda 即可!

顺便说一句,阴影 JAR 文件包含数千个单独的.class文件,而组装后的 ZIP 文件仅包含少数 JAR 文件。尽管整体大小(以字节为单位)较大,但文件数量会少得多,从而减少冷启动时间。我还没有在 AWS 云上对此进行测试,但在我的 LocalStack 上,冷启动时间从大约 1 分钟缩短到了 6 秒 - 绝对是开发的一个很好的推动器。