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 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-blank5.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 null | security-annotation 默认 disable + 无 CDI | tobago-config.xml 配 ignore |
| 官方示例部署 500(JspViewDeclarationLanguage) | 示例假定应用服务器自带 JSTL | WEB-INF/lib 补 jstl-api 1.2.1 与 jstl-impl 1.2.5 |
| writeState 抛 DatatypeConverter NPE | JDK 9+ 缺 JAXB | WEB-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" |
六、参考链接
- Tobago 主题文档:https://tobago.apache.org/themes.html
- Tobago 官方 Demo(可在线切换主题):https://tobago-vm.apache.org/tobago-example-demo/
- tobago-theme-scarborough 坐标:https://repo1.maven.org/maven2/org/apache/myfaces/tobago/tobago-theme-scarborough/
- tobago-example-blank 坐标:https://repo1.maven.org/maven2/org/apache/myfaces/tobago/tobago-example-blank/
- JSF Ajax(jsf.js)规范:https://jakarta.ee/specifications/faces/2.3/
- 本专栏前置篇:《Apache Tobago 5.15.2 快速入门与集成示例》《Apache Tobago 5.15.2 常用组件与配置详解》