CentOS 8 安装 Apache Tapestry 指南

在 CentOS 8 服务器部署 Apache Tapestry 5.9.1 的完整指南,覆盖 JDK 17 与 Maven 安装、quickstart 工程生成、jakarta 构件修正、Jetty 启动与 curl 页面验证全流程。

Apache TapestryApache Tapestry2026/10/12

CentOS 8 安装 Apache Tapestry 指南

一、环境信息

本指南全部步骤在以下环境中实测完成,命令与回显均为真实执行结果:

项目实测值
操作系统CentOS Linux 8(CentOS Linux release 8.5.1911 及以上 8.x 均适用)
内核6.8.0-138-generic(x86_64)
内存32 GB(JVM 堆限制为 256-512 MB,见 5.1 节)
网络可直连 repo.maven.apache.org(Maven Central)
JDKjava-17-openjdk-devel 1:17.0.1.0.12-2.el8_5(dnf 安装)
MavenApache Maven 3.9.9(二进制包部署于 /opt/apache-maven-3.9.9)
应用框架Apache Tapestry 5.9.1(org.apache.tapestry:tapestry-core-jakarta)
Web 服务器内嵌 Jetty 11.0.15(jetty-maven-plugin 启动)
应用目录/root/tapestry-demo
服务端口8080(HTTP)

二、前置条件

  1. 具备 root 权限的 shell 会话。
  2. 服务器可访问 Maven Central(https://repo.maven.apache.org/maven2/),构建过程中需下载 Tapestry 及其依赖。
  3. 至少 2 GB 空闲磁盘(本地 Maven 仓库约需 500 MB,构建产物约 50 MB)。
  4. dnf 包管理器可用(CentOS 8 需确保软件源指向可用仓库地址,本环境 dnf list 元数据检查正常)。

验证网络与软件源:

dnf list java-17-openjdk-devel 2>&1 | tail -3

实测回显:

Available Packages
java-17-openjdk-devel.x86_64           1:17.0.1.0.12-2.el8_5           AppStream

三、版本选型

3.1 组件版本

组件选定版本选型依据
Apache Tapestry5.9.1Maven Central 上 tapestry-core 的最新 release(构件构建时间 2026-04-06);5.9.0 起官方提供 -jakarta 后缀构件以支持 Jakarta EE
JDK17(OpenJDK 17.0.1 LTS)Tapestry 5.9.1 构件字节码为 Java 8(major 52),但配套 quickstart 工程按 release 11 编译、内嵌 Jetty 11 要求 Java 11+,选用 JDK 17 LTS 兼顾长期支持
Maven3.9.9工程 enforcer 插件要求 Maven 3.5.0+;3.9.9 与 JDK 17 兼容良好
Jetty11.0.15(内嵌)pom 中 jetty-maven-plugin 11.0.15,对应 jakarta.servlet 5.0

3.2 官方版本对照

  • 官方《Supported Environments and Versions》兼容矩阵:Tapestry 5.8.4+ 对应 Java JRE 8-21、Servlet API 3.0+;该页尚未单列 5.9.x。
  • 官方《Release Notes 5.9.0》:5.9.0 是 5.7.0+ 项目的直接替代升级,核心新特性是以 -jakarta 后缀构件的形式可选支持 jakarta.servlet(Jakarta EE)。使用 tapestry-core-jakarta 等构件时,需要 jakarta.servlet 5.0 及以上的运行环境(Jetty 11、Tomcat 10+ 等)。
  • 官方下载页目前仍以 5.8.7(2024-08-09 发布)为传统稳定线展示;5.9.x 的最新版本以 Maven Central 元数据为准(本篇实测 maven-metadata.xml 中 <release> 为 5.9.1)。
  • 官方 Getting Started 页面引用的 tapestry-simple-archetype 在 5.9 线并不存在(实测报错见 4.3 节),5.9.1 对应的脚手架构件是 org.apache.tapestry:quickstart:5.9.1。

四、部署步骤

4.1 安装 JDK 17

dnf install -y java-17-openjdk-devel curl unzip

验证安装:

java -version

实测回显:

openjdk version "17.0.1" 2021-10-19 LTS
OpenJDK Runtime Environment (build 17.0.1+12-LTS)
OpenJDK 64-Bit Server VM (build 17.0.1+12-LTS, mixed mode, sharing)

JDK 安装目录为 /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64,dnf 会自动建立 java 命令软链,无需手工配置 JAVA_HOME。

4.2 安装 Maven 3.9.9

⚠️ 建议直接使用 Maven Central 上的 Maven 发行包,路径稳定且内容与官方发布一致。实测曾从 dlcdn.apache.org 下载只得到 196 字节的异常内容(gzip 校验失败),换用 repo1.maven.org 后正常。

curl -sL -o /tmp/maven.tgz https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.tar.gz
tar -xzf /tmp/maven.tgz -C /opt
ln -sf /opt/apache-maven-3.9.9/bin/mvn /usr/local/bin/mvn
mvn -version

实测回显:

Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: /opt/apache-maven-3.9.9
Java version: 17.0.1, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "6.8.0-138-generic", arch: "amd64", family: "unix"

4.3 创建 Tapestry 工程

使用官方 quickstart 脚手架生成工程:

cd /root
mvn -B archetype:generate \
  -DarchetypeGroupId=org.apache.tapestry \
  -DarchetypeArtifactId=quickstart \
  -DarchetypeVersion=5.9.1 \
  -DgroupId=com.example \
  -DartifactId=tapestry-demo \
  -Dversion=1.0-SNAPSHOT \
  -Dpackage=com.example \
  -DinteractiveMode=false

生成完成后工程结构如下(quickstart 会同时生成 Maven 与 Gradle 两套构建文件,本篇只用 Maven,Gradle 文件可忽略或删除):

/root/tapestry-demo
├── pom.xml
├── build.gradle / build-spring-boot.gradle / settings.gradle / gradlew*   (可删)
├── pom-spring-boot.xml                                                    (可删)
└── src
    ├── main/java/com/example
    │   ├── components/Layout.java
    │   ├── pages/{About,Error404,Index,Login}.java
    │   └── services/{AppModule,DevelopmentModule,QaModule}.java
    ├── main/resources
    │   ├── com/example/pages/Index.tml、Index.properties
    │   ├── com/example/pages/Login.tml
    │   ├── com/example/components/Layout.tml
    │   └── log4j2.yml
    └── main/webapp
        └── WEB-INF/web.xml、css/、images/

⚠️ 不要使用 tapestry-simple-archetype 作为脚手架构件名。实测该构件在 Maven Central 的 5.9 线不存在,直接报错:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-archetype-plugin:3.4.1:generate (default-cli) on project standalone-pom:
The desired archetype does not exist (org.apache.tapestry:tapestry-simple-archetype:5.9.1)

4.4 修正 quickstart 生成的三处问题

⚠️ 这是 5.9.1 脚手架与官方文档未同步的坑,逐项修正后才能正常构建、启动。

第一处:maven-surefire-version 属性值为 3.1,Maven Central 上不存在该版本。直接构建会报:

[ERROR] Plugin org.apache.maven.plugins:maven-surefire-plugin:jar:3.1 or one of its dependencies could not be resolved:
[ERROR]         Could not find artifact org.apache.maven.plugins:maven-surefire-plugin:jar:3.1 in central (https://repo.maven.apache.org/maven2)

第二处:pom 默认引入的是 javax 命名空间版 tapestry-core,与内嵌 Jetty 11(jakarta.servlet 5.0)混用,启动时 Web 应用上下文初始化失败:

java.lang.NoClassDefFoundError: javax/servlet/Filter
Caused by: java.lang.ClassNotFoundException: javax.servlet.Filter

需要将 Tapestry 构件整体切换为 5.9 的 -jakarta 版本(类名不变,仍是 org.apache.tapestry5.TapestryFilter)。

第三处:生成的 src/main/webapp/WEB-INF/web.xml 仍是旧式 3.1 描述符,应改为 Jakarta EE 9 的 5.0 描述符。

一条命令完成 pom 的三处替换(surefire 版本修正 + 三个构件加 -jakarta 后缀):

cd /root/tapestry-demo
sed -i "s|<maven-surefire-version>3.1</maven-surefire-version>|<maven-surefire-version>3.1.2</maven-surefire-version>|; s|<artifactId>tapestry-core</artifactId>|<artifactId>tapestry-core-jakarta</artifactId>|; s|<artifactId>tapestry-webresources</artifactId>|<artifactId>tapestry-webresources-jakarta</artifactId>|; s|<artifactId>tapestry-test</artifactId>|<artifactId>tapestry-test-jakarta</artifactId>|" pom.xml

替换后的关键行实测:

19:        <maven-surefire-version>3.1.2</maven-surefire-version>
33:            <artifactId>tapestry-core-jakarta</artifactId>
54:            <artifactId>tapestry-webresources-jakarta</artifactId>
88:            <artifactId>tapestry-test-jakarta</artifactId>

用以下内容整体覆盖 src/main/webapp/WEB-INF/web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
         https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <display-name>Tapestry 5 Quickstart Project</display-name>
    <context-param>
        <param-name>tapestry.app-package</param-name>
        <param-value>com.example</param-value>
    </context-param>
    <context-param>
        <param-name>tapestry.development-modules</param-name>
        <param-value>com.example.DevelopmentModule</param-value>
    </context-param>
    <context-param>
        <param-name>tapestry.qa-modules</param-name>
        <param-value>com.example.QaModule</param-value>
    </context-param>
    <filter>
        <filter-name>app</filter-name>
        <filter-class>org.apache.tapestry5.TapestryFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>app</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>
    <error-page>
        <error-code>404</error-code>
        <location>/error404</location>
    </error-page>
</web-app>

另外,生成的 pom 中包含一个指向 https://repository.apache.org/content/repositories/staging/ 的 <repositories> 配置,这是发布预检用的预备仓库,正式构建用不到,可整段删除,仅保留 Maven Central。

4.5 构建

cd /root/tapestry-demo
export MAVEN_OPTS="-Xms256m -Xmx512m"
mvn -B -DskipTests package

实测关键回显:

[INFO] --- surefire:3.1.2:test (default-test) @ tapestry-demo ---
[INFO] Tests are skipped.
[INFO] --- war:3.3.1:war (default-war) @ tapestry-demo ---
[INFO] Packaging webapp
[INFO] Assembling webapp [tapestry-demo] in [/root/tapestry-demo/target/tapestry-demo-1.0-SNAPSHOT]
[INFO] Building war: /root/tapestry-demo/target/tapestry-demo-1.0-SNAPSHOT.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  06:12 min

首次构建包含全部依赖下载,耗时 6 分 12 秒;依赖进入本地仓库 /root/.m2/repository 后,增量构建实测约 4.2 秒。

⚠️ 构建阶段未加 -DskipTests 时,mvn package 会执行测试。单测可正常通过,但集成测试依赖 Selenium 浏览器环境,无图形环境的服务器会以失败结束(实测 Tests run: 24, Failures: 1,失败原因 NoClassDefFound org/openqa/selenium/bidi/HasBiDi,详见 8.4 节)。

4.6 启动内嵌 Jetty

cd /root/tapestry-demo
export MAVEN_OPTS="-Xms256m -Xmx512m"
nohup mvn -B jetty:run > /root/jetty.log 2>&1 &

等待约 40 秒后查看启动日志:

grep -aE "jetty-|Started Server|Scan interval" /root/jetty.log | tail -4

实测回显:

[INFO] jetty-11.0.15; built: 2023-04-11T18:37:53.775Z; git: 5bc5e562c8d05c5862505aebe5cf83a61bdbcb96; jvm 17.0.1+12-LTS
[INFO] Started ServerConnector@6d71f296{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
[INFO] Started Server@75b363c3{STARTING}[11.0.15,sto=0] @41397ms
[INFO] Scan interval sec = 10

Tapestry 应用初始化完成时,日志中会出现 IoC Registry 启动横幅:

2026-10-12 12:23:xx,xxx [main] INFO  org.apache.tapestry5.http.TapestryFilter Startup status:
Application 'app' (version 1.0-SNAPSHOT) startup time: 217 ms to build IoC Registry, 654 ms overall.

Scan interval sec = 10 表示开发模式下 Jetty 每 10 秒扫描一次变更并自动重载,生产环境应改用打包后的 war 部署。

4.7 验证页面渲染

验证首页:

curl -s -o /dev/null -w "INDEX HTTP %{http_code}\n" http://127.0.0.1:8080/
curl -s -o /dev/null -w "LOGIN HTTP %{http_code}\n" http://127.0.0.1:8080/login

实测回显:

INDEX HTTP 200
LOGIN HTTP 200

查看首页关键内容:

curl -s http://127.0.0.1:8080/ | grep -oE "Apache Tapestry Framework \(version [0-9.]+\)|Welcome to Tapestry 5[^<]*" | head -2

实测回显:

Apache Tapestry Framework (version 5.9.1)
Welcome to Tapestry 5! We hope that this project template will get you going in style.

验证登录页表单(页面由 com.example.pages.Login 渲染,演示账号已在本指南源码中定义为 admin / q1w2e3r4,定义方式见 5.3 节):

curl -s http://127.0.0.1:8080/login | grep -oE "<title>[^<]+</title>|action=\"[^\"]+\" method=\"post\"|placeholder=\"[^\"]+\"" | head -5

实测回显:

<title>Login | tapestry-demo</title>
action="/login.login" method="post"
placeholder="Email address"
placeholder="Password"

完整的表单提交验证(先取页面下发的 t:formdata 签名字段,再提交正确凭据,应重定向回首页并显示成功提示):

FD=$(curl -s http://127.0.0.1:8080/login | grep -oE "<input value=\"[^\"]+\" name=\"t:formdata\"" | sed "s/<input value=\"//;s/\" name=\"t:formdata\"//")
curl -sL -X POST http://127.0.0.1:8080/login.login \
  --data-urlencode "t:formdata=$FD" \
  --data-urlencode "email=admin" \
  --data-urlencode "password=q1w2e3r4" \
  --data-urlencode "submit_0=Sign in" | grep -oE "Welcome aboard!" | head -1

实测回显:

Welcome aboard!

提交错误凭据(将上面 email/password 换成 wrong@example.com 与 bad),页面会回显校验错误:

Try with user: admin
Try with password: q1w2e3r4
I'm sorry but I can't log you in!

⚠️ 提取 t:formdata 时注意:页面 HTML 中 value 属性位于 name 属性之前(<input value="..." name="t:formdata" ...>)。若正则按 name 在前的习惯书写,会取到空值,提交时 Tapestry 因缺少签名数据不回填表单字段,Login.onValidateFromLogin() 对 null 调用 equals() 而抛出 NPE,页面返回 HTTP 500(日志中可见 Cannot invoke "String.equals(Object)" because the return value of "com.example.pages.Login.conduit_get_email()" is null)。

五、配置说明

5.1 JVM 堆限制(256-512 MB)

堆参数通过 MAVEN_OPTS 注入,jetty:run 会将其带入 JVM。上面 4.5、4.6 节均已使用 -Xms256m -Xmx512m。验证进程实际生效:

ps -ef | grep "[j]ava" | head -2

实测回显:

root  922  921  64 12:23 ?  00:00:37 /usr/bin/java -Xms256m -Xmx512m -classpath /opt/apache-maven-3.9.9/boot/plexus-classworlds-2.8.0.jar -Dclassworlds.conf=/opt/apache-maven-3.9.9/bin/m2.conf -Dmaven.home=/opt/apache-maven-3.9.9 -Dlibrary.jansi.path=/opt/apache-maven-3.9.9/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/root/tapestry-demo org.codehaus.plexus.classworlds.launcher.Launcher -B jetty:run

也可以用 JDK 自带的 jps 验证:

jps -v | grep Launcher

实测回显:

  922 Launcher -Xms256m -Xmx512m -Dclassworlds.conf=/opt/apache-maven-3.9.9/bin/m2.conf -Dmaven.home=/opt/apache-maven-3.9.9 -Dlibrary.jansi.path=/opt/apache-maven-3.9.9/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/root/tapestry-demo

如需每次登录自动生效,写入 profile:

cat > /etc/profile.d/maven.sh << 'EOF'
export MAVEN_OPTS="-Xms256m -Xmx512m"
EOF
chmod +x /etc/profile.d/maven.sh

5.2 运行端口

内嵌 Jetty 的端口由 pom.xml 中 jetty-maven-plugin 的 <httpConnector><port>8080</port></httpConnector> 决定。实测通过 -Djetty.http.port=9090 覆盖无效,第二个实例仍然绑定 8080 并报:

[ERROR] Failed to execute goal org.eclipse.jetty:jetty-maven-plugin:11.0.15:run (default-cli) on project tapestry-demo: Error starting jetty: Failed to bind to 0.0.0.0/0.0.0.0:8080: Address already in use

要改端口,直接修改 pom.xml 中 <httpConnector> 的 <port> 值。

5.3 登录演示账号

quickstart 生成的 src/main/java/com/example/pages/Login.java 中内置了示例凭据校验逻辑,将其中字符串改为演示账号:

sed -i "s/users@tapestry.apache.org/admin/g; s/Tapestry5/q1w2e3r4/g" src/main/java/com/example/pages/Login.java

修改后重新 mvn -DskipTests package 并重启 jetty:run,即可用 admin / q1w2e3r4 登录(4.7 节的实测即基于该修改)。

5.4 防火墙放行

如需从其他主机访问 8080 端口:

firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload

六、升级与多版本

  • 升级 Tapestry 版本:pom.xml 中统一用属性 <tapestry-version> 管理版本号,升级只需修改该属性(如改为更高版本),再执行 mvn clean package。5.8 到 5.9 官方提供了迁移工具 tapestry-version-migrator(可执行 JAR),可批量在 pom 的 Tapestry 构件 artifactId 后追加或去除 -jakarta 后缀。
  • javax 与 jakarta 构件不可混用:5.9 线每个 Web 相关构件都有成对的普通版(javax)与 -jakarta 版(jakarta.servlet 5.0)。运行在 Jetty 11 / Tomcat 10+ 时必须全部使用 -jakarta 构件;留在 Jetty 9 / Tomcat 8-9 时则全部使用普通版。
  • 多版本并存:在同一台服务器上并存多套 Tapestry 应用时,按目录区分工程(如 /root/tapestry-demo-5.9.1、/root/tapestry-demo-5.8.7),并分别为各工程分配不同的 <httpConnector> 端口;本地 Maven 仓库 /root/.m2/repository 中各版本构件按 GAV 自然共存。

七、注意事项

  1. quickstart 同时生成 Gradle 构建文件(build.gradle、gradlew 等),与本指南 Maven 流程无关,可删除以免误导后续维护者。
  2. pom 中 apache-staging 仓库建议删除;5.9.1 所有构件均已发布到 Maven Central。
  3. 开发期 jetty:run 每 10 秒扫描重载,适合开发;生产建议使用 mvn -DskipTests package 产出的 target/tapestry-demo-1.0-SNAPSHOT.war 部署到独立 Jetty 11 或 Tomcat 10+。
  4. 不要用 app.properties 文件配置 Tapestry 符号。实测在 src/main/resources/app.properties 中写入 tapestry.production-mode=true 并重启后未生效(页面仍带开发态标记);符号配置的官方途径只有 JVM 系统属性(-D)、web.xml context-param、AppModule 贡献三种。
  5. CentOS 8 已停止维护,本指南依赖的 AppStream 源来自 8.5 存档;若 dnf 报仓库 404,需先把 repo 文件指向 vault.centos.org 的 8.5.1911 存档源再安装 JDK。
  6. 构建和运行都依赖外网拉取依赖;若服务器经代理出网,为 Maven 配置代理使用 $HOME/.m2/settings.xml 的 <proxies> 配置,不要在 shell 里混用 http_proxy 与 Maven 自身代理。

八、常见问题

Q1:启动 jetty:run 后页面返回 503,日志里有 NoClassDefFoundError: javax/servlet/Filter 怎么办? 这是 quickstart 5.9.1 生成物默认引入 javax 版 tapestry-core 导致的。定位方法:日志中 Failed startup of context o.e.j.m.p.MavenWebAppContext ... UNAVAILABLE 之下的 Caused by: java.lang.ClassNotFoundException: javax.servlet.Filter。解决:按 4.4 节把 pom 中 Tapestry 构件全部换成 -jakarta 后缀版本,并把 web.xml 升级为 jakarta 5.0 描述符,重新 package 后再启动。

Q2:mvn package 报 Could not find artifact org.apache.maven.plugins:maven-surefire-plugin:jar:3.1? 脚手架把 surefire 版本写成不存在的 3.1。把 pom.xml 属性 <maven-surefire-version> 从 3.1 改为 3.1.2(该属性同时被 surefire-junit-platform、surefire-testng 依赖引用,改一处即可),重新构建即可。

Q3:archetype:generate 报 The desired archetype does not exist (org.apache.tapestry:tapestry-simple-archetype:5.9.1)? 5.9 线没有这个构件,官方脚手架是 org.apache.tapestry:quickstart。改用 4.3 节的 -DarchetypeArtifactId=quickstart 即可。

Q4:mvn test 集成测试在服务器上失败,报 NoClassDefFound org/openqa/selenium/bidi/HasBiDi? quickstart 的 com.example.tapestry.integration.pages 下的集成测试需要 Selenium 浏览器环境。无图形环境的服务器上跑 mvn test 会执行 TestNG 套件(实测 Tests run: 24, Failures: 1, Skipped: 23,首例失败即终止后续用例),单测 com.example.unit 与 com.example.tapestry.unit 则全部通过。日常构建用 mvn package -DskipTests 跳过,或只跑单元测试:mvn test -Dtest='com.example.unit.*,com.example.tapestry.unit.*'。

Q5:登录表单提交返回 HTTP 500? 多为 t:formdata 隐藏字段未正确提交所致(Tapestry 用它携带签名后的客户端数据)。按 4.7 节的命令先取页面 t:formdata 再 POST;若日志出现 conduit_get_email() 为 null 的 NPE,说明提交体里根本没有表单字段值,检查 curl 的 --data-urlencode 是否完整带上 email、password 与 t:formdata 三个参数。

九、卸载方式

按依赖顺序清理(应用 → Maven → JDK,可选保留本地仓库缓存):

pkill -f classworlds
rm -rf /root/tapestry-demo
rm -rf /opt/apache-maven-3.9.9 /usr/local/bin/mvn /etc/profile.d/maven.sh
rm -rf /root/.m2
dnf remove -y java-17-openjdk-devel
firewall-cmd --permanent --remove-port=8080/tcp && firewall-cmd --reload

验证卸载干净:

java -version 2>&1 | head -1   # 应提示 command not found 或回落到旧版本
mvn -version 2>&1 | head -1    # 应提示 command not found

十、参考链接