代码之家  ›  专栏  ›  技术社区  ›  Andrew Rueckert

AWS Lambda使用多版本JAR中不正确的类文件?

  •  0
  • Andrew Rueckert  · 技术社区  · 3 年前

    我有一个lambda在Java8下运行了几年,我刚刚将它更新为Java11。它马上就坏了,给我带来了如下错误:

    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);
        }
    
        ...
    

    我已经 看到 Multi-Release: true ,因为我所依赖的ApacheLog4j工件为 org.apache.logging.log4j.util.StackLocator 在Java8和9+中初始化。然而,我有点希望JVM只选择类的适当版本。有什么配置需要我设置吗?是否可能将我的Lambda从Java 8切换到>爪哇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
    

    编辑: 我正在寻找 some references 这表明,当AWS Lambda提取JAR时,它们不会提取META-INF目录,该目录包含清单。MF文件,它告诉JVM JAR是一个Muli-Release JAR。Lambda是否支持多版本JAR?

    0 回复  |  直到 3 年前
        1
  •  3
  •   Maarten Brak    3 年前

    这不完全是对你问题的回答,但我希望这能有所帮助。

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

    就我而言,我使用的是 maven-shade-plugin 创建一个包含lambda函数所有依赖项的“uber”jar。本手册建议采用这种方法 official AWS documentation . 现在,这一点很重要 maven shade插件 提取所有jar文件依赖项,并将它们重新打包为一个单一的平面jar文件。如果其中一个依赖项是多版本jar(如log4j2),则可以配置 maven shade插件 重建适当的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汇编插件 在你的 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秒——这无疑是一个巨大的开发助推器。

        2
  •  0
  •   Andrew Rueckert    3 年前

    据我的AWS帐户代表称,AWS Lambdas目前不支持多版本JAR(2021-06-14)。我需要重新配置pom来构建多个工件。

    推荐文章
    MrSir  ·  无法创建java 9 JAR
    7 年前