预编译支持
目录
简介
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
打包与构建
在$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
$TOMCAT_STUFFED/pom.xml
中,然后构建shaded JARmvn 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
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无法静态链接