Apache MyFaces 4.1.3 常用工具与 API 详解
Apache MyFaces 4.1.3 常用工具与 API 详解:MyFaces 无命令行工具,以 myfaces-api/impl 两个 jar 与运行时 API 提供能力,详解 FacesServlet、FacesContext/ExternalContext、请求处理生命周期、CDI 托管 Bean、Facelets 标签库、资源处理与 myfaces-test 测试库,代码示例均在 myfaces-demo 应用实测。
Apache MyFaces 4.1.3 常用工具与 API 详解
目标:详解 Apache MyFaces Core 4.1.3 的工具形态与核心 API。MyFaces 没有独立命令行程序,全部能力以 jar 库与运行时对象提供。本文逐类速查
FacesServlet、FacesContext/ExternalContext、请求处理生命周期、CDI 托管 Bean、Facelets 标签库、资源处理与myfaces-test测试库,所有代码示例均在 Ubuntu 22.04.5 + JDK 17 + Tomcat 10.1.60 上的 myfaces-demo 应用实测运行。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- Java:OpenJDK 17.0.20.1;Maven 3.6.3
- Web 服务器:Apache Tomcat 10.1.60(
/opt/tomcat,端口 8080,堆-Xms256m -Xmx512m -Xmn128m) - MyFaces:myfaces-api 4.1.3(597 个类)+ myfaces-impl 4.1.3(1100 个类);myfaces-test 4.1.3
- 示例工程:
/root/myfaces-demo/(上下文/myfaces-demo,部署见《构建并运行示例应用指南》) - 运行地址:
http://127.0.0.1:8080/myfaces-demo/index.xhtml
二、工具概览
MyFaces 以「jar 库 + 运行时 API」形态提供,没有 myfaces 命令行可执行程序;所有能力通过引入 jar 后在 Java 代码、Facelets 页面与 Web 服务器中调用。
| 工具/API 类别 | 代表类/组件 | 作用 |
|---|---|---|
| 前端控制器 | jakarta.faces.webapp.FacesServlet | 接管 *.xhtml 请求,驱动生命周期 |
| 运行时上下文 | FacesContext、ExternalContext | 请求/响应/会话访问入口 |
| 组件树 | UIViewRoot、UIComponent | 视图组件模型 |
| 表达式语言 | EL(#{...}) | 页面绑定 Bean 属性与方法 |
| 托管 Bean | CDI(@Named/@RequestScoped) | 业务逻辑载体 |
| 视图技术 | Facelets(h:/f: 标签) | 页面模板与组件渲染 |
| 资源处理 | ResourceHandler、jakarta.faces.resource | js/css 资源分发 |
| 测试库 | myfaces-test(myfaces-test-4.1.3.jar) | 单元测试 Mock 环境 |
💡 说明:MyFaces 官方发布物中还有
myfaces-commons、myfaces-extval、myfaces-orchestra(旧版)等兄弟组件,本文聚焦核心(Core)的 myfaces-api / myfaces-impl / myfaces-test。
三、前置条件
- 已按《Ubuntu 22.04 安装 Apache MyFaces 4.1.3 指南》安装 JDK 17、Maven、Tomcat 10.1.60;
- 已按《Apache MyFaces 4.1.3 构建并运行示例应用指南》构建并部署 myfaces-demo(下文示例均来自该应用)。
四、核心 API 详解
4.1 FacesServlet:前端控制器
JSF 应用唯一的 Servlet 入口,在 web.xml 中注册并映射 *.xhtml,负责为每个请求建立 FacesContext 并驱动完整生命周期(见 4.3):
<servlet>
<servlet-name>FacesServlet</servlet-name>
<servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>FacesServlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
类位于 myfaces-api jar(实测确认存在):
6308 2026-04-30 12:37 jakarta/faces/webapp/FacesServlet.class
💡 MyFaces 还通过
org.apache.myfaces.webapp.MyFacesContainerInitializer在服务器启动时自动完成 Faces 初始化,日志可见MyFaces Core has started;load-on-startup用于确保 Servlet 提前实例化。
4.2 FacesContext 与 ExternalContext
FacesContext.getCurrentInstance() 是请求处理期间访问一切运行时对象的入口;ExternalContext 封装 Servlet 服务器的请求/响应/会话。以下方法在 myfaces-demo 的 HelloBean.getRequestInfo() 中实测运行:
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.ExternalContext;
import jakarta.servlet.http.HttpServletRequest;
public String getRequestInfo() {
FacesContext ctx = FacesContext.getCurrentInstance();
ExternalContext ext = ctx.getExternalContext();
HttpServletRequest req = (HttpServletRequest) ext.getRequest();
String sid = ext.getSession(false) != null ? ext.getSessionId(false) : "无";
return "请求方法=" + req.getMethod()
+ ";请求路径=" + ext.getRequestServletPath()
+ ";会话ID=" + sid;
}
页面实测回显:
请求信息:请求方法=GET;请求路径=/index.xhtml;会话ID=D23927D967C029978F126CC51CBC24F3
常用 API 速查:
| API | 说明 |
|---|---|
FacesContext.getCurrentInstance() | 获取当前请求的 FacesContext |
FacesContext.class.getPackage().getImplementationVersion() | 获取实现版本号(实测返回 4.1.3) |
FacesContext.getExternalContext() | 获取服务器抽象(ExternalContext) |
ExternalContext.getRequest() | 获取原生请求对象(可转型 HttpServletRequest) |
ExternalContext.getRequestServletPath() | 请求路径(实测 /index.xhtml) |
ExternalContext.getSession(boolean) | 获取/创建会话 |
ExternalContext.getSessionId(boolean) | 会话 ID |
ExternalContext.getInitParameter(String) | 读取 web.xml 配置参数 |
4.3 请求处理生命周期(六个阶段)
FacesServlet 对每个请求按固定顺序执行六阶段生命周期:
- Restore View(恢复视图):重建或恢复组件树与状态;
- Apply Request Values(应用请求值):把请求参数写入组件 submittedValue;
- Process Validations(处理校验):执行转换器/校验器,错误则跳到 Render Response;
- Update Model Values(更新模型值):把通过校验的值写入 Bean 属性;
- Invoke Application(调用应用):执行
action方法、完成导航; - Render Response(渲染响应):渲染 HTML 并保存视图状态。
💡 实测对应:POST 提交时表单携带
j_id_d_SUBMIT=1与jakarta.faces.ViewState触发完整六阶段;渲染后的页面含 ViewState 隐藏域(autocomplete="one-time-code"),供下一次 Restore View 使用。
4.4 CDI 托管 Bean 与 EL
MyFaces 4.x 托管 Bean 走 CDI:@Named(注入名) + 作用域注解(@RequestScoped/@SessionScoped/@ApplicationScoped)。myfaces-demo 的 HelloBean(节选,已实测):
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;
@Named
@RequestScoped
public class HelloBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMessage() {
if (name == null || name.trim().isEmpty()) {
return "Hello, Apache MyFaces 4.1.3!";
}
return "Hello, " + name.trim() + "! Welcome to Apache MyFaces 4.1.3.";
}
public String submit() {
return "hello";
}
}
页面中通过 EL 表达式绑定(#{} 内为属性名或方法名):
<h:outputText value="#{helloBean.facesVersion}"/>
<h:inputText id="nameInput" value="#{helloBean.name}"/>
<h:commandButton value="提交" action="#{helloBean.submit}"/>
实测渲染:#{helloBean.facesVersion} 输出 4.1.3;提交「管理员」后 #{helloBean.message} 输出 Hello, 管理员! Welcome to Apache MyFaces 4.1.3.。
⚠️ 注意:4.x 已移除旧
@ManagedBean(javax.faces.bean.*),必须用 CDI 注解且 WAR 内含WEB-INF/beans.xml(bean-discovery-mode="all"),否则 EL 解析不到 Bean。
4.5 Facelets 常用标签(h:/f:)
页面命名空间:xmlns:h="jakarta.faces.html"、xmlns:f="jakarta.faces.core"。以下标签均在 myfaces-demo 实测:
| 标签 | 作用 | 实测回显 |
|---|---|---|
<h:outputText value="..."/> | 输出文本 | 输出 Bean 属性/字面量 |
<h:form> | 表单(内含 ViewState 隐藏域) | <form id="j_id_d" ...> 携带 jakarta.faces.ViewState |
<h:inputText id="nameInput" value="..."/> | 文本输入框 | <input id="j_id_d:nameInput" name="j_id_d:nameInput" type="text"/> |
<h:commandButton value="提交" action="..."/> | 提交按钮 | <input ... type="submit" value="提交"/> |
<f:ajax render="..."/> | 局部 AJAX 刷新 | 返回 partial-response XML(见下) |
<h:link outcome="index" value="..."/> | 页面导航链接 | <a href="/myfaces-demo/index.xhtml">返回首页</a> |
f:ajax 局部刷新实测(重置按钮):
<h:commandButton value="重置">
<f:ajax render="greeting"/>
</h:commandButton>
AJAX 请求(带 Faces-Request: partial/ajax 头)返回:
<?xml version="1.0" encoding="UTF-8"?><partial-response id="j_id__v_0"><changes><update id="j_id__v_0:jakarta.faces.ViewState:1"><![CDATA[ZWU2MTM0NTIwZDk4ZjI0YjAwMDAwMDAx]]></update></changes></partial-response>
4.6 JSF 资源处理(ResourceHandler)
MyFaces 统一托管库资源(/jakarta.faces.resource/...?ln=<库名>)。实测页面 <h:head> 自动引入:
<script src="/myfaces-demo/jakarta.faces.resource/faces.js.xhtml;jsessionid=D23927D967C029978F126CC51CBC24F3?ln=jakarta.faces"></script>
直接验证资源可访问:
curl -s -o /dev/null -w "faces_js=%{http_code} type=%{content_type} size=%{size_download}\n" "http://127.0.0.1:8080/myfaces-demo/jakarta.faces.resource/faces.js.xhtml?ln=jakarta.faces"
实测回显:
faces_js=200 type=text/javascript size=108945
💡 自己工程内的静态资源(如
src/main/webapp/resources/css/site.css)以<h:outputStylesheet library="css" name="site.css"/>引用,同样经资源处理器带缓存与版本管理分发;资源排除列表由jakarta.faces.RESOURCE_EXCLUDES控制(默认.xhtml等后缀不托管)。
4.7 myfaces-test:测试工具库
myfaces-test(org.apache.myfaces.core:myfaces-test:4.1.3)提供脱离 Web 服务器的 Mock 运行环境,用于 JSF 应用单元测试。jar 已实测可从 Maven Central 获取(251913 字节),内含 Mock 表达式与 EL 基础设施:
org/apache/myfaces/test/el/MockELContext.class
org/apache/myfaces/test/el/MockExpressionFactory.class
org/apache/myfaces/test/el/MockValueExpression.class
org/apache/myfaces/test/el/MockVariableMapper.class
用法:在测试工程中以 test 作用域引入:
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-test</artifactId>
<version>4.1.3</version>
<scope>test</scope>
</dependency>
💡 说明:myfaces-test 提供 EL 解析、资源包等 Mock 对象供
mvn test阶段构造FacesContext单元测试(不需要启动 Tomcat);界面级联调仍建议用真实部署环境(本环境 Tomcat 10.1.60)验证。
五、注意事项
- JVM 堆限制:Tomcat 进程堆固定
-Xms256m -Xmx512m -Xmn128m,通过jps -v复核生效值;页面渲染输出大时优先调RESOURCE_BUFFER_SIZE/FACELETS_BUFFER_SIZE,不要盲目加堆。 - 不要手工写死客户端 ID:表单 ID(如
j_id_d)由 MyFaces 运行时生成;需要稳定 ID 时给组件显式id属性。 - AJAX 请求必须带头与参数:
Faces-Request: partial/ajax+javax.faces.partial.ajax=true+ 当前页 ViewState(见 4.5)。 - Bean 作用域选择:无状态计算用
@RequestScoped;跨请求状态用@SessionScoped;全局常量用@ApplicationScoped。状态保存在服务端会话,注意NUMBER_OF_VIEWS_IN_SESSION上限。 - API 向后兼容:4.x 只认
jakarta.*命名空间;引用了javax.faces.*的旧代码无法在 4.1.3 编译运行。
六、常见问题
Q1:FacesContext.getCurrentInstance() 返回 null?
该 API 只能在 FacesServlet 处理的请求内调用(页面渲染、action/监听器方法)。在过滤器、@PostConstruct 或普通线程中调用会得到 null,需先确认调用时机或改用 CDI 注入上下文。
Q2:EL 解析 #{bean.property} 报 PropertyNotFoundException?
① Bean 必须是 @Named + 作用域注解;② 属性必须有标准 getter(getName() 对 #{bean.name});③ WEB-INF/beans.xml 存在且 discovery-mode 为 all;④ 确认没有重复类名导致 CDI 歧义。
Q3:为什么页面 HTML 里表单 id 是 j_id_d 这种名字?
组件未显式指定 id 时由 MyFaces 自动生成短 id。需要稳定 id(供 JS/测试定位)时给组件加 id="..." 属性;VIEW_UNIQUE_IDS_CACHE_ENABLED(默认 true)会缓存生成结果提升性能。
Q4:f:ajax 不触发或返回 406?
局部 AJAX 请求必须带 Faces-Request: partial/ajax 请求头与 javax.faces.partial.ajax=true 参数,且 ViewState 必须来自当前页面(见 4.5 节实测命令)。
Q5:myfaces-test 引入后单元测试仍报 ClassNotFound?
确认依赖作用域为 test 且版本与实现一致(4.1.3);myfaces-test 只提供 Mock 环境,页面渲染/服务器特性(Servlet API)仍需 jakarta.servlet-api(provided)参与编译。
七、参考链接
- Apache MyFaces 官网:https://myfaces.apache.org/
- FacesContext / ExternalContext API 文档:https://jakarta.ee/specifications/faces/apidocs/
- Jakarta Faces 教程(生命周期与标签):https://jakarta.ee/learn/docs/jakartaee-tutorial/latest/jsf-facelets/
- myfaces-test(Maven Central):https://central.sonatype.com/artifact/org.apache.myfaces.core/myfaces-test