Apache MyFaces 4.1.3 构建并运行示例应用指南

Apache MyFaces 4.1.3 构建并运行示例应用指南:从 pom.xml 依赖、web.xml FacesServlet 注册、CDI 托管 Bean 到 index.xhtml 页面,mvn package 构建 WAR、部署 Tomcat 10.1.60,GET 页面回显、POST 表单提交、AJAX 局部刷新与资源加载全部实测。

Apache MyFacesApache MyFaces2026/10/12

Apache MyFaces 4.1.3 构建并运行示例应用指南

目标:从零构建一个基于 Apache MyFaces Core 4.1.3 的最小 JSF Web 应用并部署运行。本文覆盖工程结构、pom.xml 依赖、web.xml 注册 FacesServlet、CDI 托管 Bean、Facelets 页面编写,以及 mvn package 构建、Tomcat 10.1.60 部署与 GET/POST/AJAX/资源加载的完整运行验证。所有命令与回显均在 Ubuntu 22.04.5 + JDK 17 + Tomcat 10.1.60 环境实测通过。

一、环境信息

  • 操作系统: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,systemd 托管)
  • MyFaces:myfaces-api 4.1.3 + myfaces-impl 4.1.3;CDI:weld-servlet-shaded 5.1.2.Final
  • 示例工程:/root/myfaces-demo/;应用上下文路径:/myfaces-demo
  • 演示访问地址:http://127.0.0.1:8080/myfaces-demo/index.xhtml

二、前置条件

  • 已按《Ubuntu 22.04 安装 Apache MyFaces 4.1.3 指南》安装 JDK 17、Maven、Tomcat 10.1.60 并配置好 /root/.m2/settings.xml 国内镜像;
  • Tomcat 正在运行(systemctl is-active tomcat 返回 active)。

三、创建工程结构

mkdir -p /root/myfaces-demo/src/main/java/com/example
mkdir -p /root/myfaces-demo/src/main/webapp/WEB-INF

工程目录结构如下:

/root/myfaces-demo/
├── pom.xml
└── src/main/
    ├── java/com/example/HelloBean.java
    └── webapp/
        ├── index.xhtml
        ├── hello.xhtml
        └── WEB-INF/
            ├── beans.xml
            └── web.xml

四、编写工程文件

4.1 pom.xml:声明 MyFaces 依赖

myfaces-api 与 myfaces-impl 以 compile 作用域引入(会随 WAR 打包进 WEB-INF/lib);jakarta.servlet-api 由 Tomcat 提供,用 provided;weld-servlet-shaded 提供 CDI(4.x 托管 Bean 必需):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>myfaces-demo</artifactId>
  <version>1.0</version>
  <packaging>war</packaging>
  <name>MyFaces 4.1.3 Demo</name>

  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.myfaces.core</groupId>
      <artifactId>myfaces-api</artifactId>
      <version>4.1.3</version>
    </dependency>
    <dependency>
      <groupId>org.apache.myfaces.core</groupId>
      <artifactId>myfaces-impl</artifactId>
      <version>4.1.3</version>
    </dependency>
    <dependency>
      <groupId>jakarta.servlet</groupId>
      <artifactId>jakarta.servlet-api</artifactId>
      <version>6.0.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.jboss.weld.servlet</groupId>
      <artifactId>weld-servlet-shaded</artifactId>
      <version>5.1.2.Final</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>myfaces-demo</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>3.4.0</version>
      </plugin>
    </plugins>
  </build>
</project>

⚠️ 注意:Maven 3.6.3 默认绑定的 maven-war-plugin 2.2 在 JDK 17 下会报 Cannot access defaults field of Properties,必须像上面一样显式指定 3.4.0 以上版本。

4.2 web.xml:注册 FacesServlet 与配置参数

<?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_6_0.xsd"
         version="6.0">

  <display-name>myfaces-demo</display-name>

  <!-- 标准 Jakarta Faces 配置参数 -->
  <context-param>
    <param-name>jakarta.faces.PROJECT_STAGE</param-name>
    <param-value>Production</param-value>
  </context-param>

  <!-- Apache MyFaces 配置参数 -->
  <context-param>
    <param-name>org.apache.myfaces.CONFIG_REFRESH_PERIOD</param-name>
    <param-value>2</param-value>
  </context-param>
  <context-param>
    <param-name>org.apache.myfaces.CACHE_EL_EXPRESSIONS</param-name>
    <param-value>alwaysRecompile</param-value>
  </context-param>
  <context-param>
    <param-name>org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION</param-name>
    <param-value>25</param-value>
  </context-param>
  <context-param>
    <param-name>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</param-name>
    <param-value>false</param-value>
  </context-param>
  <context-param>
    <param-name>org.apache.myfaces.LOG_WEB_CONTEXT_PARAMS</param-name>
    <param-value>true</param-value>
  </context-param>

  <!-- FacesServlet:JSF 前端控制器 -->
  <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>

  <welcome-file-list>
    <welcome-file>index.xhtml</welcome-file>
  </welcome-file-list>
</web-app>

💡 org.apache.myfaces.LOG_WEB_CONTEXT_PARAMS=true 会在启动日志中打印全部配置参数与默认值(见《配置参数详解》),生产环境建议关闭。CACHE_EL_EXPRESSIONS=alwaysRecompile 用于开启视图池(View Pooling)。

4.3 beans.xml:启用 CDI

MyFaces 4.x 的托管 Bean 依赖 CDI,需在 WEB-INF/beans.xml 声明 Bean 归档:

<?xml version="1.0" encoding="UTF-8"?>
<beans 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/beans_4_0.xsd"
       version="4.0" bean-discovery-mode="all">
</beans>

4.4 托管 Bean:HelloBean.java

使用 CDI 注解 @Named + @RequestScoped,演示 EL 取值、方法调用与 FacesContext/ExternalContext API:

package com.example;

import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.ExternalContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.inject.Named;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@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 getServerTime() {
        LocalDateTime now = LocalDateTime.now();
        return now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

    public String submit() {
        return "hello";
    }

    public String getFacesVersion() {
        return FacesContext.class.getPackage().getImplementationVersion();
    }

    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;
    }
}

4.5 Facelets 页面

index.xhtml(首页:显示版本/时间/请求信息 + 表单提交):

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:f="jakarta.faces.core">
<h:head>
  <title>Apache MyFaces 4.1.3 Demo</title>
</h:head>
<h:body>
  <h1>Apache MyFaces 4.1.3 Demo</h1>
  <p>Jakarta Faces 实现版本:<h:outputText value="#{helloBean.facesVersion}"/></p>
  <p>服务器当前时间:<h:outputText value="#{helloBean.serverTime}"/></p>
  <p>请求信息:<h:outputText value="#{helloBean.requestInfo}"/></p>

  <h:form>
    <h:outputLabel for="nameInput" value="请输入姓名:"/>
    <h:inputText id="nameInput" value="#{helloBean.name}"/>
    <h:commandButton value="提交" action="#{helloBean.submit}"/>
    <h:commandButton value="重置">
      <f:ajax render="greeting"/>
    </h:commandButton>
    <p/>
    <h:outputText id="greeting" value="#{helloBean.message}" styleClass="greeting"/>
  </h:form>
</h:body>
</html>

hello.xhtml(导航目标页:提交后跳转并回显消息):

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html">
<h:head>
  <title>Hello Page</title>
</h:head>
<h:body>
  <h1>导航结果页</h1>
  <p>这是导航目标页面 hello.xhtml。</p>
  <p><h:outputText value="#{helloBean.message}"/></p>
  <h:link outcome="index" value="返回首页"/>
</h:body>
</html>

💡 submit() 返回字符串 hello 触发隐式导航(implicit navigation):Faces 自动拼接为 hello.xhtml 并重定向渲染;h:link outcome="index" 反向导航回 index.xhtml。

五、构建 WAR

5.1 执行构建

cd /root/myfaces-demo
mvn -DskipTests package

实测回显(节选):

[INFO] Copying webapp resources [/root/myfaces-demo/src/main/webapp]
[INFO] Building war: /root/myfaces-demo/target/myfaces-demo.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  20.993 s
[INFO] ------------------------------------------------------------------------

产物确认:

ls -l /root/myfaces-demo/target/myfaces-demo.war

实测回显:

-rw-r--r-- 1 root root 6409342 myfaces-demo.war

5.2 确认依赖已打包进 WAR

unzip -l /root/myfaces-demo/target/myfaces-demo.war | grep "WEB-INF/lib"

实测回显:

WEB-INF/lib/
WEB-INF/lib/jakarta.annotation-api-2.1.1.jar
WEB-INF/lib/myfaces-api-4.1.3.jar
WEB-INF/lib/weld-servlet-shaded-5.1.2.Final.jar
WEB-INF/lib/myfaces-impl-4.1.3.jar

依赖树核对:

cd /root/myfaces-demo && mvn dependency:tree

实测回显:

com.example:myfaces-demo:war:1.0
+- org.apache.myfaces.core:myfaces-api:jar:4.1.3:compile
+- org.apache.myfaces.core:myfaces-impl:jar:4.1.3:compile
+- jakarta.servlet:jakarta.servlet-api:jar:6.0.0:provided
\- org.jboss.weld.servlet:weld-servlet-shaded:jar:5.1.2.Final:compile
   \- jakarta.annotation:jakarta.annotation-api:jar:2.1.1:compile

六、部署与启动

6.1 部署 WAR

Tomcat 支持热部署:把 WAR 拷贝到 webapps/ 目录即自动解压部署(也可用 Manager 页面 admin/q1w2e3r4 上传):

cp /root/myfaces-demo/target/myfaces-demo.war /opt/tomcat/webapps/

6.2 确认 MyFaces 初始化日志

等待自动部署完成后查看启动日志:

grep -E "MyFaces|WELD" /opt/tomcat/logs/catalina.out | tail -6

实测回显:

org.jboss.weld.event.ExtensionObserverMethodImpl.checkRequiredTypeAnnotations WELD-000411: Observer method [BackedAnnotatedMethod] org.apache.myfaces.config.annotation.CdiAnnotationProviderExtension.processAnnotatedType(@Observes ProcessAnnotatedType<T>) receives events for all annotated types. Consider restricting events using @WithAnnotations or a generic type with bounds.
org.apache.myfaces.webapp.MyFacesContainerInitializer.onStartup Using org.apache.myfaces.webapp.MyFacesContainerInitializer
org.apache.myfaces.util.ExternalSpecifications.lambda$static$0 MyFaces Core Bean Validation support disabled
org.apache.myfaces.application.ApplicationImpl.getProjectStage Couldn't discover the current project stage, using Production
org.apache.myfaces.webapp.FacesInitializerImpl.initFaces MyFaces Core has started, it took [654] ms.

💡 MyFaces Core has started 说明 MyFaces 4.1.3 初始化完成;首个请求到达时还会输出 MyFaces Core: Servlet 6.0 support enabled 与 EL 解析器链。

七、运行验证

7.1 GET 访问首页

curl -s http://127.0.0.1:8080/myfaces-demo/index.xhtml

实测回显(完整页面):

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head>
  <title>Apache MyFaces 4.1.3 Demo</title><script src="/myfaces-demo/jakarta.faces.resource/faces.js.xhtml;jsessionid=D23927D967C029978F126CC51CBC24F3?ln=jakarta.faces"></script></head><body>
  <h1>Apache MyFaces 4.1.3 Demo</h1>
  <p>Jakarta Faces 实现版本:4.1.3</p>
  <p>服务器当前时间:2026-10-11 19:46:46</p>
  <p>请求信息:请求方法=GET;请求路径=/index.xhtml;会话ID=D23927D967C029978F126CC51CBC24F3</p><form id="j_id_d" name="j_id_d" method="post" action="/myfaces-demo/index.xhtml;jsessionid=D23927D967C029978F126CC51CBC24F3" enctype="application/x-www-form-urlencoded"><label for="j_id_d:nameInput">请输入姓名:</label><input id="j_id_d:nameInput" name="j_id_d:nameInput" type="text" value="" /><input id="j_id_d:j_id_g" name="j_id_d:j_id_g" type="submit" value="提交" /><input id="j_id_d:j_id_h" name="j_id_d:j_id_h" type="submit" value="重置" onclick="faces.util.chain(this, event,function(event){myfaces.ab(this,event,'action','','j_id_d:greeting',{})}); return false;" />
    <p></p><span id="j_id_d:greeting" class="greeting">Hello, Apache MyFaces 4.1.3!</span><input type="hidden" name="j_id_d_SUBMIT" value="1" /><input type="hidden" name="jakarta.faces.ViewState" id="j_id__v_0:jakarta.faces.ViewState:1" value="MDI4MTE5ZGNjYzMzYTczYjAwMDAwMDAx" autocomplete="one-time-code" /></form></body>
</html>

验证要点:

  • 版本回显 Jakarta Faces 实现版本:4.1.3(取自 FacesContext 包实现版本号,证明运行的是 MyFaces 4.1.3);
  • 请求信息行来自 FacesContext/ExternalContext API(见 4.4 节);
  • 页面自动引入 MyFaces 的 faces.js 资源并渲染表单、ViewState 隐藏域;
  • 客户端 ID 由组件树自动生成(j_id_d:nameInput 等),不要手工写死。

7.2 POST 表单提交与导航回显

按顺序:先 GET 取回页面、Cookie 与 ViewState,再以表单字段回传(客户端 ID 从页面中提取):

cd /tmp
curl -s -c mf_cookies.txt http://127.0.0.1:8080/myfaces-demo/index.xhtml > mf_index.html
VS=$(grep -oE 'name="jakarta.faces.ViewState" id="[^"]+" value="[^"]+"' mf_index.html | grep -oE 'value="[^"]+"' | cut -d'"' -f2)
curl -s -b mf_cookies.txt -X POST http://127.0.0.1:8080/myfaces-demo/index.xhtml \
  --data-urlencode "j_id_d=index.xhtml" \
  --data-urlencode "j_id_d:nameInput=管理员" \
  --data-urlencode "j_id_d:j_id_g=提交" \
  --data-urlencode "j_id_d_SUBMIT=1" \
  --data-urlencode "jakarta.faces.ViewState=$VS"

实测回显(提交「管理员」后经 submit() 导航到 hello.xhtml 并回显):

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head>
  <title>Hello Page</title></head><body>
  <h1>导航结果页</h1>
  <p>这是导航目标页面 hello.xhtml。</p>
  <p>Hello, 管理员! Welcome to Apache MyFaces 4.1.3.</p><a href="/myfaces-demo/index.xhtml">返回首页</a></body>
</html>

7.3 AJAX 局部刷新验证

“重置”按钮通过 f:ajax render="greeting" 发起局部请求(Faces-Request: partial/ajax),返回 partial-response XML:

cd /tmp
curl -s -c mf_c4.txt http://127.0.0.1:8080/myfaces-demo/index.xhtml > mf_i4.html
VS=$(grep -oE 'name="jakarta.faces.ViewState" id="[^"]+" value="[^"]+"' mf_i4.html | grep -oE 'value="[^"]+"' | cut -d'"' -f2)
curl -s -b mf_c4.txt -X POST http://127.0.0.1:8080/myfaces-demo/index.xhtml \
  -H "Faces-Request: partial/ajax" \
  --data-urlencode "j_id_d=index.xhtml" \
  --data-urlencode "javax.faces.partial.ajax=true" \
  --data-urlencode "javax.faces.source=j_id_d:j_id_h" \
  --data-urlencode "javax.faces.partial.execute=j_id_d:j_id_h" \
  --data-urlencode "javax.faces.partial.render=j_id_d:greeting" \
  --data-urlencode "j_id_d_SUBMIT=1" \
  --data-urlencode "jakarta.faces.ViewState=$VS"

实测回显:

<?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>

7.4 JSF 资源加载验证

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

7.5 JVM 堆参数生效确认

/usr/lib/jvm/java-17-openjdk-amd64/bin/jps -v | grep Bootstrap

实测回显(节选,堆区间与 setenv.sh/systemd 配置一致):

5629 Bootstrap ... -Xms256m -Xmx512m -Xmn128m -Djava.security.egd=file:/dev/urandom ...

八、注意事项

  • JVM 堆限制:Tomcat 进程堆必须控制在 256–512MB(-Xms256m -Xmx512m -Xmn128m),修改位置有两处且需保持一致——/opt/tomcat/bin/setenv.sh 与 /etc/systemd/system/tomcat.service 的 Environment=CATALINA_OPTS;改后用 jps -v 复核实际生效值。
  • CDI 不可省略:MyFaces 4.x 移除 @ManagedBean 旧机制,Bean 必须走 CDI(@Named + @RequestScoped)且 WAR 中要有 beans.xml,否则 EL 表达式无法解析到 Bean 属性。
  • 客户端 ID 动态生成:表单/组件 ID 由 MyFaces 运行时生成(如 j_id_d),页面里应通过 id 属性显式命名重要组件(如 nameInput),不要依赖自动 ID。
  • Project Stage:默认 Production;开发期建议 jakarta.faces.PROJECT_STAGE=Development 以显示更详细的错误页。
  • WAR 名称即上下文路径:myfaces-demo.war → http://127.0.0.1:8080/myfaces-demo/。

九、常见问题

Q1:mvn package 报 maven-war-plugin:2.2 的 Cannot access defaults field of Properties?

Maven 3.6.3 默认 war 插件过旧、不兼容 JDK 17。在 <build><plugins> 中显式指定 maven-war-plugin 3.4.0(见 4.1 节)。

Q2:页面显示但 Bean 属性全部为空或报 PropertyNotFoundException?

① 确认 @Named 导入的是 jakarta.inject.Named、@RequestScoped 是 jakarta.enterprise.context.RequestScoped;② 确认 WEB-INF/beans.xml 存在且 bean-discovery-mode="all";③ 确认 weld-servlet-shaded 在 WEB-INF/lib 中(见 5.2 节)。

Q3:部署后访问 /myfaces-demo/ 返回 404?

/myfaces-demo/ 根路径依赖 welcome-file 转发,应直接访问 /myfaces-demo/index.xhtml;确认 index.xhtml 位于 WAR 根目录(非 WEB-INF 内)。

Q4:提交后没有跳转 hello.xhtml?

submit() 必须返回字符串 hello(与目标文件名对应)并保证方法签名 public String submit();若方法返回 null 或 void 则停留在当前页。

Q5:AJAX 请求返回 406 或空响应?

AJAX 局部请求必须带 Faces-Request: partial/ajax 头与 javax.faces.partial.ajax=true 参数(见 7.3 节),且携带当前页的 ViewState。

Q6:想改端口或加 HTTPS?

改 /opt/tomcat/conf/server.xml 的 Connector port 属性(默认 8080);HTTPS 需另配 SSL Connector,然后重启 systemctl restart tomcat。

十、参考链接