介绍

Tomcat 支持使用 GraalVM/Mandrel Native Image 工具生成包含容器的原生二进制文件。本文档页面介绍了此类镜像的构建过程。

设置

本机映像工具更易于与单个 JAR 一起使用,因此该过程将使用 Maven shade 插件 JAR 打包。 这个想法是生成一个 JAR,其中包含来自 Tomcat、Web 应用程序和所有其他依赖项的所有必要类。 尽管 Tomcat 已收到兼容性修复以支持本机映像,但其他库可能不兼容,可能需要替换代码(GraalVM 文档提供了有关此内容的更多详细信息)。

下载并安装 GraalVM 或 Mandrel。

https://github.com/apache/tomcat/tree/main/modules/stuffed 下载 Tomcat Stuffed 模块。 为方便起见,可以设置 env 属性:

export TOMCAT_STUFFED=/absolute...path...to/stuffed

构建过程现在需要 Apache Ant 和 Maven。

打包和构建

$TOMCAT_STUFFED 文件夹中,目录结构与常规 Tomcat 的目录结构相同。 主要配置文件放在 conf 文件夹中,如果使用默认`server.xml`则 webapps 放在 webapps 文件夹中。

在 JSP 预编译步骤中,所有 webapp 类都需要提供给 Maven shade 插件和编译器。 /WEB-INF/lib 中存在的任何 JAR 都需要作为 Maven 依赖项提供。 webapp-jspc.ant.xml 脚本会将类从 Web 应用程序的 /WEB-INF/classes 文件夹, 复制到 Maven 用作编译目标的 target/classes 路径,但如果任何 JSP 源使用它们,则需要将它们打包为 JAR。

第一步是构建包含所有依赖项的着色 Tomcat JAR。 Web 应用程序中的任何 JSP 都必须全部预编译和打包(假设 Web 应用程序包含`$WEBAPPNAME` Web 应用程序):

cd $TOMCAT_STUFFED
mvn package
ant -Dwebapp.name=$WEBAPPNAME -f webapp-jspc.ant.xml

现在,应将 Web 应用程序的依赖项添加到主 $TOMCAT_STUFFED/pom.xml,然后构建阴影 JAR:

mvn package

由于最好在提前编译中尽可能避免使用反射,因此最好从主 server.xml 配置以及 用于配置上下文的 context.xml 文件中生成和编译 Tomcat Embedded 代码。

$JAVA_HOME/bin/java\
        -Dcatalina.base=. -Djava.util.logging.config.file=conf/logging.properties\
        -jar target/tomcat-stuffed-1.0.jar --catalina -generateCode src/main/java

然后停止 Tomcat 并使用以下命令包含生成的嵌入式代码:

mvn package

此处描述的过程的其余部分将假设此步骤已完成,并且 --catalina -useGeneratedCode 参数已添加到命令行中。如果不是这种情况,他们应该被删除。

本机映像配置

本机映像不支持任何形式的动态类加载或反射,除非在 Descriptors 中明确定义。 生成跟踪令牌会使用 GraalVM 中的跟踪代理,并且在某些情况下需要额外的手动配置。

使用 GraalVM Substrate VM 及其跟踪代理运行 Tomcat:

$JAVA_HOME/bin/java\
        -agentlib:native-image-agent=config-output-dir=$TOMCAT_STUFFED/target/\
        -Dorg.graalvm.nativeimage.imagecode=agent\
        -Dcatalina.base=. -Djava.util.logging.config.file=conf/logging.properties\
        -jar target/tomcat-stuffed-1.0.jar --catalina -useGeneratedCode

现在,所有来自 Web 应用程序且导致动态类加载的路径(例如:Servlet 访问、websockets 等) 都需要使用执行 Web 应用程序的脚本进行访问。Servlet 可以在启动时加载,而不需要实际访问。 侦听器还可用于在启动时加载其他类。完成后,可以停止 Tomcat。

描述符现在已在代理程序输出目录中生成。此时,必须进行进一步的配置以添加未跟踪的项目, 包括:基本接口、资源包、基于 BeanInfo 的反射等。有关此过程的更多信息,请参阅 Graal 文档。

即使使用的所有类都必须将 AOT 编译到本机映像中,Web 应用程序仍必须保持不变, 并继续在 WEB-INF 文件夹中包含所有需要的类和 JAR。尽管这些类实际上不会运行或加载,但需要访问它们。

构建本机映像

如果一切都已正确完成,现在可以使用 native-image 工具构建原生镜像。

$JAVA_HOME/bin/native-image --report-unsupported-elements-at-runtime\
        --enable-http --enable-https --enable-url-protocols=http,https,jar,jrt\
        --initialize-at-build-time=org.eclipse.jdt,org.apache.el.parser.SimpleNode,jakarta.servlet.jsp.JspFactory,org.apache.jasper.servlet.JasperInitializer,org.apache.jasper.runtime.JspFactoryImpl\
        -H:+UnlockExperimentalVMOptions\
        -H:+JNI -H:+ReportExceptionStackTraces\
        -H:ConfigurationFileDirectories=$TOMCAT_STUFFED/target/\
        -H:ReflectionConfigurationFiles=$TOMCAT_STUFFED/tomcat-reflection.json\
        -H:ResourceConfigurationFiles=$TOMCAT_STUFFED/tomcat-resource.json\
        -H:JNIConfigurationFiles=$TOMCAT_STUFFED/tomcat-jni.json\
        -jar $TOMCAT_STUFFED/target/tomcat-stuffed-1.0.jar

附加的 --static 参数在生成的二进制文件中启用 glibc、zlib 和 libstd++ 的静态链接。

然后,运行本机映像是:

./tomcat-stuffed-1.0 -Dcatalina.base=.-Djava.util.logging.config.file=conf/logging.properties --catalina -useGeneratedCode

兼容性

Servlet、JSP、EL、websockets、Tomcat 容器、tomcat-native、 HTTP/2 都可以在本机映像中开箱即用地提供支持。

在编写本文档时,不支持 JULI,因为 Graal 不支持日志管理器配置属性, 此外还存在一些静态初始化器问题,应改用常规的 java.util.logging 记录器和实现。

如果使用默认 server.xml 文件,则必须从配置中删除一些 Server 侦听器, 因为它们与本机映像不兼容,例如 JMX 侦听器(不支持 JMX)和泄漏预防侦听器(使用 Graal 中不存在的内部代码)。

缺少更好的 Tomcat 功能的项目:

  • java.util.logging LogManager:未通过系统属性进行配置,因此必须使用标准 java.util.logging 而不是 JULI

  • 静态链接配置:tomcat-native 不能静态链接