预编译支持

目录

简介

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

设置

原生镜像工具在使用单个JAR时会更加方便,因此该过程将使用Maven shade插件进行JAR打包。其理念是生成一个包含Tomcat、Web应用和所有额外依赖项所需所有类的单个JAR。尽管Tomcat已获得兼容性修复以支持原生镜像,但其他库可能不兼容,并可能需要替换代码(GraalVM文档中对此有更多详细信息)。

下载并安装GraalVM或Mandrel。

https://github.com/apache/tomcat/tree/main/modules/stuffed下载Tomcat Stuffed模块。为方便起见,可以设置一个环境变量

export TOMCAT_STUFFED=/absolute...path...to/stuffed
构建过程现在需要Apache Ant和Maven。

打包与构建

$TOMCAT_STUFFED文件夹内,目录结构与常规Tomcat相同。主配置文件位于conf文件夹中,如果使用默认的server.xml,则Web应用位于webapps文件夹中。

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

第一步是构建包含所有依赖项的shaded Tomcat JAR。Web应用中的所有JSP都必须进行预编译和打包(假设webapps包含一个$WEBAPPNAME Web应用)

cd $TOMCAT_STUFFED
mvn package
ant -Dwebapp.name=$WEBAPPNAME -f webapp-jspc.ant.xml
现在应该将Web应用的依赖项添加到主$TOMCAT_STUFFED/pom.xml中,然后构建shaded JAR
mvn package

由于在预编译(Ahead of Time compilation)中最好尽可能避免使用反射,因此从主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参数已添加到命令行中。如果不是这种情况,则应将其移除。

原生镜像配置

原生镜像不支持任何形式的动态类加载或反射,除非在描述符中明确定义。生成它们需要使用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

兼容性

Servlets、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无法静态链接