Apache Tobago 5.15.2 主题与进阶实战详解

实测 Apache Tobago 5.15.2 主题机制与进阶实战,覆盖 scarborough/speyside/standard 三主题关系、web.xml 与 tobago-config 两种切换方式、多主题 jar 并存的合并排序行为、Ajax 提交协议与 Secret 令牌、4.6.0 与 6.x 运行时选型,全部命令与回显实测通过。

Apache TobagoApache Tobago2026/10/15

Apache Tobago 5.15.2 主题与进阶实战详解

目标:吃透 Apache Tobago 5.15.2 的主题机制——三主题的继承关系、两种切换方式与多主题 jar 并存时的实测行为——并覆盖 Ajax 提交协议、Secret 防伪令牌、视图状态保存、Tobago 4.6.0(JDK 8)与 6.x(Jakarta)运行时选型等进阶实战点;所有结论与回显均已在 Ubuntu 22.04 + OpenJDK 17 + Tomcat 9.0.123 部署实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04 LTS(x86_64)
  • JDK:OpenJDK 17.0.20.1(主测)/ OpenJDK 1.8.0_504(4.6.0 回退实测)
  • 应用服务器:Apache Tomcat 9.0.123(javax)
  • Tobago:5.15.2(主测)、4.6.0(JDK 8 路线实测)
  • 主题 jar:tobago-theme-standard、tobago-theme-scarborough、tobago-theme-speyside(均 5.15.2)
  • 实测工程:/opt/tobago-demo(第 3 篇工程扩展而来)
  • 对照应用:官方示例 tobago-example-blank 5.15.2(上下文 /blank2)
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/tobago-demo/
200

二、主题机制原理

2.1 三个主题的继承关系

Tobago 5.x 官方提供三个可直接使用的主题,Maven 坐标均为 org.apache.myfaces.tobago:tobago-theme-<name>:5.15.2:

主题定位依赖关系(实测 dependency:tree)
standard基础主题:Bootstrap 栅格 + tobago.js/jsf.js + 图标无父主题
scarborough经典浅色主题(默认推荐)依赖 standard
speyside商务蓝灰主题依赖 standard

实测依赖树(mvn dependency:tree 节选):

[INFO] +- org.apache.myfaces.tobago:tobago-theme-speyside:jar:5.15.2:compile
[INFO] |  \- org.apache.myfaces.tobago:tobago-theme-standard:jar:5.15.2:compile
[INFO] +- org.apache.myfaces.tobago:tobago-theme-scarborough:jar:5.15.2:compile

渲染时主题 CSS 分层叠加:基础层(bootstrap-icons、jsf.js、tobago.js)永远来自 standard,主题层 CSS 来自当前主题。官方示例 blank 一次引入了全部五个主题 jar(含 charlotteville、roxborough),其 WEB-INF/lib 实测清单:

tobago-theme-charlotteville-5.15.2.jar
tobago-theme-roxborough-5.15.2.jar
tobago-theme-scarborough-5.15.2.jar
tobago-theme-speyside-5.15.2.jar
tobago-theme-standard-5.15.2.jar

2.2 页面加载了什么

当前主题为 scarborough 时的主题资源引用(curl 实测):

<link rel='stylesheet' href='/tobago-demo/tobago/scarborough/5.15.2/css/tobago.css' type='text/css'>
<link rel='stylesheet' href='/tobago-demo/tobago/standard/5.15.2/css/bootstrap-icons.css' type='text/css'>
<script src='/tobago-demo/tobago/standard/5.15.2/js/jsf.js' type='text/javascript'>
<script src='/tobago-demo/tobago/standard/5.15.2/js/tobago.js' type='module'>

资源路径规则:/<应用上下文>/tobago/<主题名>/<主题版本>/...,由 Tobago 的资源加载器统一吐出,主题 jar 升级后版本号路径变化、浏览器缓存自然失效。

三、主题切换实战

3.1 方式一:web.xml 参数(最常用)

修改 web.xml 中 org.apache.myfaces.tobago.config.theme 的值后重新打包部署即可。

切到 speyside(<param-value>speyside</param-value>)实测:

cd /opt/tobago-demo
mvn -q -B clean package
# 重新部署后
curl -s http://127.0.0.1:8080/tobago-demo/ | sed 's/></>\n</g' | grep stylesheet
<link rel='stylesheet' href='/tobago-demo/tobago/standard/5.15.2/css/bootstrap-icons.css' type='text/css'>
<link rel='stylesheet' href='/tobago-demo/tobago/speyside/5.15.2/css/tobago.css' type='text/css'>

CSS 从 scarborough/5.15.2/ 变为 speyside/5.15.2/,切换生效。前提是 pom 中已引入对应主题 jar(scarborough 与 speyside 二选一或都引入)。

3.2 方式二:tobago-config.xml 的 default-theme

Tobago 支持在 WEB-INF/tobago-config.xml 里声明默认主题(官方示例即此写法):

<theme-config>
  <default-theme>speyside</default-theme>
</theme-config>

官方 blank 应用带五个主题 jar 且 default-theme 为 speyside,实测渲染:

<link rel='stylesheet' href='/blank2/tobago/standard/5.15.2/css/bootstrap-icons.css' type='text/css'>
<link rel='stylesheet' href='/blank2/tobago/speyside/5.15.2/css/tobago.css' type='text/css'>

default-theme 生效,speyside 主题 CSS 被选用。

💡 技术拓展:主题选型建议。scarborough 是官方文档与示例的默认选择,组件覆盖最全;speyside 适合后台管理类界面的稳重配色;charlotteville/roxborough 为 5.x 新增的现代扁平风格。所有主题共享同一套组件树与 JS API,切换主题只影响渲染层,业务代码零改动——这是 Tobago「主题化组件框架」定位的直接体现。

3.3 多主题 jar 并存的合并排序行为(重要实测)

实测发现一个容易被误解的行为:当 classpath 中同时存在多个主题 jar 时,请求 standard 主题并不会真的只渲染 standard。

实验:保留 scarborough + speyside 两个主题 jar,web.xml 主题参数改为 standard:

curl -s http://127.0.0.1:8080/tobago-demo/ | grep -oE "href='[^']*tobago.css'" | head -1
href='/tobago-demo/tobago/speyside/5.15.2/css/tobago.css'

请求 standard,实际渲染 speyside。再只保留 scarborough(移除 speyside 依赖)同样请求 standard:

href='/tobago-demo/tobago/scarborough/5.15.2/css/tobago.css'

实测结论:多主题 jar 并存时,Tobago 会把主题按依赖与合并顺序组织成主题链,实际渲染取链上最高层主题,web.xml 参数为 standard 时会被「顶替」;要精确使用某个主题,保持该主题与 standard 在 classpath、移除其余主题 jar 是最稳妥的做法。这也解释了官方示例同时带五个主题 jar 时为何必须显式配 default-theme。

四、进阶实战

4.1 Ajax 提交协议与 Secret 令牌

Tobago 命令组件默认走 JSF 标准 Ajax(partial 请求)。从渲染出的表单域(实测 DOM)可以还原完整协议:

<input type='hidden' name='javax.faces.source' id='javax.faces.source' disabled='disabled'>
<input type='hidden' name='page::lastFocusId' id='page::lastFocusId::field'>
<input type='hidden' name='org.apache.myfaces.tobago.webapp.Secret' id='org.apache.myfaces.tobago.webapp.Secret' value='HJb3rjEmN6zOVdQIclTmpw=='>
<input type='hidden' name='javax.faces.ViewState' id='j_id__v_0:javax.faces.ViewState:1' value='QUMwRjlDMUJDOTlGNzlCQzAwMDAwMDAx' autocomplete='off'>
<input type='hidden' name='javax.faces.RenderKitId' value='tobago'>

一次按钮点击实际发出的 POST 参数(浏览器开发者工具与 curl 模拟双验证一致):

javax.faces.partial.ajax=true
javax.faces.source=page:submit
javax.faces.behavior.event=action
javax.faces.partial.execute=@all
javax.faces.partial.render=@all
javax.faces.ViewState=<服务端签发的状态串>
page:name=<输入值>
page:submit=提交
org.apache.myfaces.tobago.webapp.Secret=<页面隐藏域中的值>

Secret 是 Tobago 的会话级防伪令牌:服务端在会话创建时生成(SecretSessionListener),提交时由 SecretPhaseListener 在 RESTORE_VIEW 阶段校验。实测用错误的 Secret 提交,请求返回 200 但六个阶段只剩 RESTORE_VIEW 与 RENDER_RESPONSE(值应用被跳过、页面按初始状态重渲染),现象极具迷惑性;因此写接口级自动化测试时必须维护会话 Cookie 并原样回传 Secret 与 ViewState。会话令牌可通过配置关闭(blank 示例注释区所示):<create-session-secret>false</create-session-secret>、<check-session-secret>false</check-session-secret>,生产环境不建议关闭。

4.2 PROJECT_STAGE 与自动消息区

web.xml 把 javax.faces.PROJECT_STAGE 从 Production 改为 Development 后重新部署,tc:page 会自动渲染匿名消息区(实测 DOM <tobago-messages id='j_id_2:j_id_3'>),校验错误、组件告警直接显示在页面上,是联调期的首选配置;生产切回 Production。

4.3 视图状态保存与横向扩容

JSF 默认 server 状态保存(视图树存 session,页面只传状态串)。实测单会话视图状态串长度约 32 字符,服务端开销可控;若多实例部署且无会话粘滞,改客户端保存:

<context-param>
  <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
  <param-value>client</param-value>
</context-param>

客户端模式下状态串进入表单隐藏域(体积明显增大),需要配合 org.apache.myfaces.viewstate.Secret 状态加密配置,按安全要求评估取舍。

4.4 运行时选型一:JDK 8 环境用 Tobago 4.6.0

Tobago 5.15.2 含 112 个 Java 17 字节码类,JDK 8 无法运行(实测结论见第 3 篇)。JDK 8 环境的等效方案是 Tobago 4.6.0,改动只有三处:tobago.version 改 4.6.0、编译 release=8、tobago-config.xml 的 version 改 4.0(4.x 解析器不认 5.1,实测报 SAXException: Using unknown version attribute '5.1')。

实测部署(OpenJDK 1.8.0_504 + Tomcat 9.0.123):

java -version 2>&1 | head -1
openjdk version "1.8.0_504"
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/tobago-demo4/
200

主题资源路径(4.x 的主题链为 scarborough → standard → tobago-bootstrap):

href='/tobago-demo4/tobago/scarborough/tobago-bootstrap/4.6.0/css/bootstrap.css'

表单回显实测(同一套 Bean 与页面,POST 后):

POST HTTP 200
--- 回显检查 ---
你好, q1w2e3r4! (来自 Tobago 回显)
input type='text' name='page:name' id='page:name::field' value='q1w2e3r4' class='form-control' required='required'/

4.6.0 与 5.15.2 的 API 几乎一致,切换成本主要在配置版本号上。

4.5 运行时选型二:Jakarta/Tomcat 10.1 等 Tobago 6.x

Tobago 6.x(当前 latest 6.12.2)才是 Jakarta Faces 路线。对 Maven Central 上 tobago-core-6.12.2.jar 的字节码实测:jakarta/faces 引用 351 处、jakarta/servlet 6 处、javax/faces 与 javax/servlet 均为 0,配套 MyFaces 4.0.4 + Tomcat 10.1.61 + web-app 6.0(jakarta 命名空间)+ faces-config 4.0 + 页面命名空间 jakarta.faces.core。如果新建项目且基础设施允许 Tomcat 10.1,直接从 6.x 起步可避免未来迁移;5.x → 6.x 的迁移本质是 javax → jakarta 的全局替换加配置版本升级,本文全部 javax 引用点(web.xml 的 Servlet 类名、faces-config 命名空间、页面 f: 命名空间、依赖坐标)都要同步更新。

4.6 安全注解与 CDI 的边界

security-annotation 三个取值的实测语义:

取值行为适用
disable(5.x 默认)渲染命令组件时执行 CDI 授权检查有 CDI 的应用服务器(WildFly、Weld 化 Tomcat)
hide无权限组件直接从渲染树隐藏同上,需隐藏语义时
ignore跳过授权检查无 CDI 的纯 Tomcat(本文环境必配)

配置位置在 WEB-INF/tobago-config.xml,且按 tobago-config-5.1.xsd 序列必须位于文件末尾元素区——实测插错位置会导致 schema 校验失败、整份配置被静默拒绝。

五、排错经验汇总

现象实测根因处理
切主题后 CSS 不变多主题 jar 并存,渲染取主题链顶端移除多余主题 jar 或配 default-theme
POST 返回 200 但表单数据丢失Secret 或 ViewState 不匹配,值应用被跳过保持会话与页面参数一致(自动化测试必查)
页面 500 authorizationHelper is nullsecurity-annotation 默认 disable + 无 CDItobago-config.xml 配 ignore
官方示例部署 500(JspViewDeclarationLanguage)示例假定应用服务器自带 JSTLWEB-INF/lib 补 jstl-api 1.2.1 与 jstl-impl 1.2.5
writeState 抛 DatatypeConverter NPEJDK 9+ 缺 JAXBWEB-INF/lib 或 pom 补 jaxb-api 2.3.1
4.x 工程启动报 unknown version attribute '5.1'tobago-config 版本号与主线不匹配4.x 用 4.0,5.x 用 5.1
tc:link 点击 500 ViewId must start with a '/'outcome 缺少前导斜杠写 outcome="/demo.xhtml"

六、参考链接