[英]AWS Lambda using incorrect classfiles from a Multi-Release JAR?
我有一个 lambda 在 Java8 下运行了几年,我刚刚将它更新为 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
有问题的 class 并不是特别令人兴奋,并且有一个简单的 static 初始化,这在我的课程中很常见:
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 时,我以前见过这样的问题; I needed to flag my jar's manifest as Multi-Release: true
, because the ApacheLog4j artifact that I depend on provides alternate implementations for the org.apache.logging.log4j.util.StackLocator
class in Java 8- and 9+. 但是,我有点希望 JVM 能够选择合适的 class 版本。 我必须在某处设置一些配置吗? 是否有可能将我的 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
Edit: I am finding some references indicating that, when AWS Lambda extracts a JAR, they don't extract the META-INF directory, which contains the MANIFEST.MF file that tells the JVM that the JAR is a Muli-Release JAR. Lambdas 是否支持 Multi-Release JARs?
不完全是您问题的答案,但我希望这可能会有所帮助。
您的分析是正确的 - AWS lambda 提取了整个 JAR 文件。 Then the JVM running the lambda function doesn't recognize the code as a JAR file anymore and effectively the entire META-INF directory is ignored.
就我而言,我使用maven-shade-plugin
创建了一个“uber”-jar,其中包含我的 lambda function 的所有依赖项。 AWS 官方文档中推荐使用此方法。 现在 - 这很重要 - maven-shade-plugin
提取所有 jar 文件依赖项并将它们重新打包到单个平面 jar 文件中。 If one of your dependencies is a multi-release jar (as is log4j2), then you can configure the maven-shade-plugin
to reconstruct an appropriate META-INF directory and if you run the jar as a jar file then everything still works. 但是因为 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>
然后你需要在你的pom.xml
中配置maven-assembly-plugin
:
<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,瞧!
As an aside - whereas the shaded JAR file contained thousands of individual .class
files, the assembled ZIP file contains only a handful of JAR files. 即使整体大小(以字节为单位)更大,文件的数量也会少得多,从而减少冷启动时间。 我还没有在 AWS 云上测试过,但是在我的 LocalStack 上,冷启动从大约 1 分钟下降到 6 秒——这绝对是开发的一个很好的助推器。
根据我的 AWS 客户代表的说法,AWS Lambda 目前不支持 Multi-Release JARs (2021-06-14)。 相反,我将需要重新配置我的 pom 以构建多个工件。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.