JSF(Java Server Faces)是一个以组件为中心的服务器端用户界面构建框架。JSF和现在比较流行的Struts2、SpringMVC不同,它采用了基于事件驱动的编程模型,这使得JSF使用起来比较像编写桌面端程序,而不是基于HTTP协议的Web程序,这使得JSF的开发效率是非常高的。当然,这个优点也同样是缺点,这种开发方式生成的前端代码灵活性不高,对于十分个性化的页面实现起来比较困难。
随着HTML5的发展和流行,JSF这种开发模式已经开始趋于落后。不过JSF仍然有其使用场景,对于那些大型、复杂的企业级 Web 应用程序,JSF仍然是一个常见的选择,它与JavaEE技术栈能够无缝集成,使得开发更加简便;对于需要高度稳定可靠的内部管理系统如ERP、CRM等,JSF也是一个不错的选择,它的组件化特性和可扩展性使得开发人员能够快速构建各种类型的管理界面。
注意:由于JSF是个比较古老的技术,而现在已经并不流行,许多书的JSF版本还是JSF1.x,这和我们现在使用的JSF2.x区别较大,本系列笔记基于最新的JavaEE8版本和Wildfly14应用服务器编写。
这篇笔记我们简单了解JSF工程的配置和使用。
在具体使用JSF框架前,我们需要了解JSF中的几个核心概念。
视图:JSF中,视图通常是XHTML页面,它们定义了应用程序的用户界面,用于访问视图,视图则寻找自己关联的托管Bean用来填充数据,用户在视图上进行点击按钮等操作时,视图也会调用对应的托管Bean来执行业务逻辑。
托管Bean(ManagedBean):JSF框架中,用户的操作最终会调用托管Bean并更新其中的字段,而托管Bean还包含了具体处理数据的业务逻辑,它能和视图相关联并将字段数据显示在视图上。一个视图可能对应多个托管Bean,一个托管Bean也可以被多个页面使用。
UI组件(UIComponent):JSF提供了一系列预定义的UI组件,如文本框、按钮和表格,视图的编写其实就是组合这些组件标签的过程。
除此之外,JSF还具有以下功能特性。
转换器:转换器用于在UI组件和后端数据之间转换数据格式,例如日期和时间的格式化和解析。
验证器:验证器用于检查用户输入的数据是否符合特定的规则,如长度、格式和范围限制。
事件驱动:JSF支持事件驱动编程模型,允许开发者为组件添加事件监听器以响应用户交互。
AJAX:JSF支持AJAX,它能够通过异步HTTP请求允许页面部分更新而无需重新加载整个页面,此外使用JSF我们不必编写复杂的JavaScript代码处理JSON和XML报文以及实现异步HTTP请求,这些都由框架帮我们完成。有关JSF AJAX的内容将在后续章节介绍。
最后还要说明的是,JSF是基于Servlet技术的,JSF仅仅是一个表现层MVC框架,这对于一个完整的Web应用程序来说其实是远远不够的,在使用JSF框架时我们可能还涉及一些Servlet组件的编写,它们通常是结合使用的。例如当我们需要使用HTTP Header而非Cookie保持会话并使用Redis集中存储会话信息时,我们可能就得编写一个Filter来进行额外的处理。
我们都熟悉MVC架构,用户查看视图(View)并做一些操作,控制器(Controller)将操作导致的数据变化更新到模型(Model),模型内数据的变化又驱动了视图的改变。
JSF框架本质也是MVC架构的,不过JSF和SpringMVC等框架设计上有些区别。JSF中,ManagedBean既可以包含数据也可以包含业务逻辑(尤其是在处理页面事件和用户交互方面),因此,ManagedBean在某种程度上混合了MVC中C和M的职责,不过在一些被设计为更严格的MVC架构工程中,也可能会将业务逻辑和数据操作抽取到单独的类中,而将ManagedBean作为纯粹的控制器来处理用户界面逻辑和数据绑定。不过一般来说,我们通常习惯于将那些对应于页面展示的数据如显示区域、表格、表单等作为ManagedBean的字段,而那些对应于数据库的数据模型单独抽取到Java类中,这些单独的JavaBean也可能被用于经过一些处理后填充ManagedBean的字段。总而言之,这取决于应用程序的架构和设计偏好。
至于视图通常则是由XHTML标记语言编写,JSF还提供了一些UI组件和Facelet技术用于简化视图的开发,具体将在后续章节介绍。
这里我们以一个例子的形式介绍JSF工程的搭建和开发流程,工程目录结构如下,它遵循标准的JavaWeb工程目录结构。
|_ src/main
|_ java
|_ com.gacfox.demo.demojsf
|_ controller
|_ LoginManagedBean.java
|_ model
|_ User.java
|_ webapps
|_ WEB-INF
|_ web.xml
|_ login.xhtml
|_ success.xhtml
JSF的接口包含在JavaEE SDK中,我们直接引入即可,注意其作用域是provided
,在Wildfly14下JSF的默认实现是Mojarra
。
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0.1</version>
<scope>provided</scope>
</dependency>
我们需要在web.xml
中编写以下配置。
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="false">
<display-name>Demo JSF</display-name>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>FacesServlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>FacesServlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
</web-app>
代码中我们首先配置了javax.faces.PROJECT_STAGE
,将其设置为Development
表示目前为开发阶段,JSF能够输出更多友好的调试信息,在生产环境该值应设置为Production
。此外我们还配置了FacesServlet
,JSF框架是基于Servlet构建的,该Servlet可以看作JSF核心功能的入口,我们配置匹配*.faces
的请求URL由JSF框架处理。
这里我们的业务逻辑非常简单,用户输入用户名和密码并进行校验,校验成功跳转到成功页面,校验失败依然将登录页面返回给用户,并显示错误信息。
LoginManagedBean.java
package com.gacfox.demo.demojsf.controller;
import com.gacfox.demo.demojsf.model.User;
import com.gacfox.demo.demojsf.service.UserService;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
@Named("login")
@RequestScoped
public class LoginManagedBean {
@Inject
private UserService userService;
private String username;
private String password;
private String errMessage;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getErrMessage() {
return errMessage;
}
public void setErrMessage(String errMessage) {
this.errMessage = errMessage;
}
public String login() {
User user = userService.queryUserByUsername(username);
if (user != null && user.getPassword().equals(password)) {
return "success";
} else {
errMessage = "用户名或密码错误!";
return "login";
}
}
}
上面代码定义了一个LoginManagedBean
类,它其实也可以看作一个控制器类,其中login()
方法就是登陆的处理逻辑,它的返回值是字符串,对应于XHTML视图的文件名。此外还要注意类上标注的两个注解,@Named
注解用于标注CDI将这个类的实例注入JSF框架,注入后我们的XHTML视图才能和ManagedBean相关联,@RequestScoped
则表示这个对象的作用域是请求作用域。
代码中我们还使用@Inject
依赖注入了一个userService
,它用于具体查询数据库并返回用户信息。
login.xhtml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>JSF Demo</title>
</h:head>
<h:body>
<h:form>
<label for="username">用户名</label>
<h:inputText id="username" value="#{login.username}"/>
<label for="password">密码</label>
<h:inputText id="password" value="#{login.password}"/>
<h:commandButton value="btn" action="#{login.login()}"/>
</h:form>
<p>
<h:outputText value="#{login.errMessage}"/>
</p>
</h:body>
</html>
上面页面代码中,我们引入了一些xmlns命名空间,引入它们后我们才能在JSF中使用相关的标签。上面页面中,主要使用了h:inputText
和h:commandButton
这两个UI控件,分别是文本输入框和表单提交按钮。注意两个控件上的value
和action
属性,它们将控件和实体类进行了数据和事件的绑定,其中#{}
写法使用了EL表达式,这和JSP的${}
差不多,login
对应于ManagedBean的名字,login
的属性和方法调用也都是对应的。
success.xhtml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>JSF Demo</title>
</h:head>
<h:body>
<p>
Login Success!
</p>
</h:body>
</html>
这个页面没什么特别的,就是显示一个成功信息。
将JSF工程打包为War后部署到Wildfly即可,假设工程的ContextPath是demojsf
,我们使用浏览器访问http://localhost:8080/demojsf/login.faces
就可以看到我们编写的登陆页面了。
输入错误的用户名和密码将提示错误信息。
输入正确的信息后,将跳转到登陆成功页面。