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 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/ExternalContextAPI(见 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。
十、参考链接
- Apache MyFaces 官网:https://myfaces.apache.org/
- MyFaces Core 4.1 文档(含示例工程):https://myfaces.apache.org/core41/
- Jakarta Faces 标签库(h:/f:)参考:https://jakarta.ee/learn/docs/jakartaee-tutorial/latest/jsf-facelets/
- Weld(CDI 参考实现)Servlet:https://weld.cdi-spec.org/