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 MyFacesApache MyFaces2026/10/12

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 属性与方法
托管 BeanCDI(@Named/@RequestScoped)业务逻辑载体
视图技术Facelets(h:/f: 标签)页面模板与组件渲染
资源处理ResourceHandler、jakarta.faces.resourcejs/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 对每个请求按固定顺序执行六阶段生命周期:

  1. Restore View(恢复视图):重建或恢复组件树与状态;
  2. Apply Request Values(应用请求值):把请求参数写入组件 submittedValue;
  3. Process Validations(处理校验):执行转换器/校验器,错误则跳到 Render Response;
  4. Update Model Values(更新模型值):把通过校验的值写入 Bean 属性;
  5. Invoke Application(调用应用):执行 action 方法、完成导航;
  6. 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)参与编译。

七、参考链接