1

我在 Java8 下运行了几年的 lambda,我刚刚将它更新到 Java 11。它立即坏了,给了我如下错误:

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

有问题的类并不是特别令人兴奋,并且有一个简单的静态初始化,这在我的类中很常见:

public class NonClosingOutputStream extends ProxyOutputStream {
    private static final Logger log = LogManager.getLogger(); // Line 11

    public NonClosingOutputStream(final OutputStream proxy) {
        super(proxy);
    }

    ...

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

jar/META-INF/versions:

versions/
├── 11
│   └── org
│       └── glassfish
│           └── jersey
│               └── internal
│                   └── jsr166
│                       ├── JerseyFlowSubscriber$1.class
│                       ├── JerseyFlowSubscriber.class
│                       ├── SubmissionPublisher$1.class
│                       ├── SubmissionPublisher$2.class
│                       ├── SubmissionPublisher$3.class
│                       ├── SubmissionPublisher$4.class
│                       ├── SubmissionPublisher$5.class
│                       ├── SubmissionPublisher$6.class
│                       ├── SubmissionPublisher.class
│                       └── SubmissionPublisherFactory.class
└── 9
    ├── module-info.class
    └── org
        └── apache
            └── logging
                └── log4j
                    ├── core
                    │   └── util
                    │       └── SystemClock.class
                    └── util
                        ├── Base64Util.class
                        ├── ProcessIdUtil.class
                        ├── StackLocator.class
                        └── internal
                            └── DefaultObjectInputFilter.class

编辑:我发现一些参考资料表明,当 AWS Lambda 提取 JAR 时,它们不会提取 META-INF 目录,该目录包含 MANIFEST.MF 文件,该文件告诉 JVM JAR 是 Muli-Release JAR。Lambda 是否支持 Multi-Release JAR?

4

2 回答 2

2

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

您的分析是正确的 - 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>

然后你需要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>

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

顺便说一句- 阴影 JAR 文件包含数千个单独的.class文件,而组装的 ZIP 文件仅包含少数 JAR 文件。即使整体大小(以字节为单位)更大,文件的数量也会少得多,从而减少冷启动时间。我没有在 AWS 云上测试过,但是在我的 LocalStack 上,冷启动从大约 1 分钟下降到 6 秒——这绝对是开发的一个很好的助推器。

于 2021-06-15T00:47:33.123 回答
0

根据我的 AWS 客户代表的说法,AWS Lambda 目前不支持 Multi-Release JAR (2021-06-14)。相反,我将需要重新配置我的 pom 以构建多个工件。

于 2021-06-14T19:49:04.343 回答