2.3 JSF使用入门
下面以一个简单用户登录用例作为示例,来简单介绍JSF的用法入门。
2.3.1 从输入页面开始
前面已经介绍了如何在JSP页面中增加JSF标签库定义,一旦为JSP页面增加了标签库定义,接下来就可使用JSF标签(这就是JSF的UI组件)来开发JSP页面了。关于各标签的具体用法可以参考本书后面内容,此处先使用几个最简单的JSF标签来开发JSP页面。
下面是本应用的输入页面代码。
程序清单:codes\02\2.3\jsfqs\login.jsp
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <!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"> <!-- 该句加载在classes下的messages的国际化资源文件 --> <f:loadBundle basename="messages" var="msg"/> <html> <head> <title>登录</title> </head> <body> <!-- 开始使用JSF的视图输出 --> <f:view> <h3> <!-- 输出国际化资源文件中的国际化信息 --> <h:outputText value="#{msg.loginHeader}"/> </h3> <!-- 输出login Bean的err属性 --> <b><h:outputText value="#{login.err}"/></b> <h:form id="loginForm"> <!-- 输出国际化资源文件中的国际化信息 --> <h:outputText value="#{msg.namePrompt}"/> <!-- 将下面单行输入框的值绑定到login Bean的name属性 --> <h:inputText value="#{login.name}" /><br/> <!-- 输出国际化资源文件中的国际化信息 --> <h:outputText value="#{msg.passPrompt}"/> <!-- 将下面单行输入框的值绑定到login Bean的pass属性 --> <h:inputText id="pass" value="#{login.pass}"/><br/> <!-- 将下面按钮的动作绑定到login Bean的valid方法 --> <h:commandButton action="#{login.valid}" value="#{msg. buttonTitle}" /> <!-- ① --> </h:form> </f:view> </body> </html>
从上面的页面代码中可以看到:JSF的输入页面已经不再是传统的HTML页面了,它几乎完全使用了JSF标签来生成页面效果。这些JSF标签就是一个又一个的页面组件——如果使用支持JSF的IDE工具,开发者能以拖放控件的方式来开发JSP页面。JSF框架会负责将这些页面组件转换为与之对应的HTML标签。关于这些JSF标签的用法,本书后面会有更详细的介绍。
从上面①号代码也可以看出,使用JSF标签来定义输入页面时,已经不再需要为表单指定action属性来作为表单提交的URL。而是可以直接将每个JSF标签绑定到托管Bean的属性,或者绑定到托管Bean的方法,从而将页面的视图组件与后台的Bean实现关联——这就类似于在RAD开发中为控件编写脚本了。
需要指出的是,如果用户直接请求访问这个JSP页面将会出现错误,这是因为该页面中大量使用了JSF标签,因此该页面必须先经过JSF核心控制器处理,否则就会出现错误。
为了保证用户请求被JSF核心控制器拦截,用户请求必须匹配*.jsf模式,因此我们向login.jsf发送请求,也就是使用浏览器访问http://localhost:9999/jsfqs/login.jsf(其中9999是JBoss的服务端口)。
为了简化用户访问,我们可以为应用增加一个index.jsp页面,该页面只做一件事情:将用户请求转发到login.jsf。index.jsp页面内容如下:
程序清单:codes\02\2.3\jsfqs\index.jsp
<jsp:forward page="login.jsf"/>
在浏览器中访问该应用,将看到如图2.5所示的页面。
图2.5 JSF快速入门的登录页面
除此之外,上面页面中还用到了国际化支持,其中:
<f:loadBundle.../>标签用于临时加载国际化资源的语言包。
<h:outputText.../>标签用于输出指定key对应的国际化消息。
表面上看起来,这个页面非常简单,页面里包含了两个单行文本框和一个登录按钮。但通过在浏览器中查看源代码,发现该表单的代码非常复杂,而且该表单里还包含了一个隐藏域,这个隐藏域通常是如下形式的代码:
<input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="H4sIAAAAAAAAA...." />
上面隐藏域的value值通常非常长,而且在我们看来完全是一堆乱码,但这正是JSF实现客户端保存状态的关键,JSF解决了Web应用状态不连续的弊端,它将客户端状态通过隐藏域的形式保存起来。不过对于普通开发而言,这些细节无须理会,JSF已经为我们全部封装好了。
2.3.2 开发托管Bean
从作用上看,托管Bean非常类似Struts 2的Action,但作用模式则有所不同。对于Struts 2的Action而言,应用通过表单提交的方式把请求提交到Struts 2的Action——这还需要开发者理解请求—响应架构;但对于JSF的托管Bean而言,系统直接将JSF中UI组件的行为绑定到托管Bean的属性或方法。例如,上面代码中的绑定:
<h:inputText value="#{login.name}" />:该文本框的行为绑定到login Bean的name属性。
<h:commandButton action="#{login.valid}" value="#{msg.buttonTitle}" />:该按钮的行为直接绑定到login Bean的valid方法。
这可以说是JSF与Struts 2的最大差别之一:JSF的UI组件行为可以直接绑定到服务器端代码——就像为VB、Delphi程序中控件编写事件脚本一样。
本应用的托管Bean的代码如下:
程序清单:codes\02\2.3\jsfqs\WEB-INF\src\lee\LoginBean.java
public class LoginBean
{
//下面的三个属性都会直接与JSF标签绑定
private String name;
private String pass;
private String err;
//此处省略了name、pass、err属性的setter和getter方法
...
//该方法被绑定到UI组件(按钮)的action属性
public String valid()
{
if (name.equals("crazyit") && pass.equals("leegang"))
{
return "success";
}
setErr("您的用户名和密码不符合");
return "failure";
}
}
上面代码定义了一个LoginBean,这是一个托管Bean,但这个托管Bean完全是一个POJO,每个方法都没有丝毫独特之处;处理用户请求的valid方法名也可以是任意的,只要该方法可以返回一个字符串即可。
完成托管Bean定义之后,还必须使用配置文件来配置该托管Bean。配置托管Bean使用标准JSF配置文件完成,配置该托管Bean的代码如下:
程序清单:codes\02\2.3\jsfqs\WEB-INF\faces-config-beans.xml
<?xml version="1.0" encoding="GBK"?>
<!-- JSF配置文件的根元素,并指定Schema信息 -->
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
version="1.2">
<!-- 配置托管Bean -->
<managed-bean>
<!-- 设置托管Bean的名字 -->
<managed-bean-name>login</managed-bean-name>
<!-- 设置托管Bean的实现类 -->
<managed-bean-class>lee.LoginBean</managed-bean-class>
<!-- 设置托管Bean实例的有效范围 -->
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>
从上面配置文件中看到,该托管Bean的名字为login,这就是前面绑定JSF标签所使用的托管Bean的名字。下面是前面将UI组件行为绑定到托管Bean的一行代码:
<h:inputText id="pass" value="#{login.pass}"/><br/>
上面代码将一个单行文本框的行为绑定到login托管Bean的pass属性,JSF正是通过这种方式简化了HTTP细节的处理。
除此之外,输入页面还有如下绑定:
<h:commandButton action="#{login.valid}" value="#{msg.buttonTitle}" />
上面代码将一个按钮的action行为(即用户单击)绑定到login托管Bean的valid方法。也就是说,当用户单击该按钮时,会返回login托管Bean的valid方法的返回值,可以理解该返回值是一个逻辑视图名。
2.3.3 定义导航规则
当JSF应用里页面上UI组件触发托管Bean某个方法返回一个字符串之后,这个字符串实际就代表了一个逻辑视图,这个逻辑视图与任何具体的视图资源分离,这样就需要开发者为逻辑视图配置具体的视图资源了。
JSF以导航规则来决定逻辑视图和物理视图资源之间的对应关系——不仅如此,JSF的导航规则还可指定系统从哪个页面开始,以该页面的提交按钮绑定的值作为逻辑视图,导航规则定义了这些逻辑视图和实际视图资源之间的对应关系。
JSF同样使用标准的配置文件来定义导航规则,下面是本应用中的导航规则定义文件。
程序清单:codes\02\2.3\jsfqs\WEB-INF\faces-config-nav.xml
<?xml version="1.0" encoding="GBK"?> <!-- JSF配置文件的根元素,并指定Schema信息 --> <faces-config xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd" version="1.2"> <navigation-rule> <!-- 导航规则的输入页面 --> <from-view-id>/login.jsp</from-view-id> <!-- 如果login.jsp提交的命令结果是success, 则跳转到视图页greeting.jsp --> <navigation-case> <from-outcome>success</from-outcome> <to-view-id>/greeting.jsp</to-view-id> </navigation-case> <!-- 如果login.jsp提交的命令结果是failure, 则跳转到视图页login.jsp --> <navigation-case> <from-outcome>failure</from-outcome> <to-view-id>/login.jsp</to-view-id> </navigation-case> </navigation-rule> </faces-config>
上面的导航规则配置文件配置了一个完整规则——从from-view-id确定的JSF页面:login.jsp,如果该页面提交结果的值是success,则跳到greeting.jsp;如果该页面提交结果的值是failure,则跳到login.jsp。
从上面的分析可以看出,JSF的页面标签比传统的表单提交更加灵活。在传统的Web应用里,每个表单对应一次事件,当表单提交时触发submit事件,而单个表单域则无法单独触发事件。
而JSF则允许每个表单域单独触发事件,JSF中最常用的两类事件是:
Value Change事件:检测表单域的值的改变,当表单域的值改变时,触发事件。
Action事件:用户单击按钮或超链接时可触发这类事件。
前面的输入页面当用户单击按钮时触发Action事件,从而触发login托管Bean的valid方法,该方法根据login托管Bean的name和pass属性返回一个字符串(逻辑视图)。实际上,login托管Bean的name和pass被绑定到页面中两个文本框,也就是说,用户的输入决定了login托管Bean的name和pass属性值,也就决定了系统返回的逻辑视图。
如果用户在“用户名”输入框中输入crazyit,在“密码”输入框中输入leegang,则看到如图2.6所示的页面。
图2.6 登录成功页面
上面页面一样大量使用了JSF的页面标签,将需要动态输出的内容绑定到login托管Bean的属性,或者绑定到国际化资源文件的指定key。下面是该页面的代码。
程序清单:codes\02\2.3\jsfqs\greeting.jsp
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!--该句绑定在classes下的messages的资源文件-->
<f:loadBundle basename="messages" var="msg"/>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>登录成功</title>
</head>
<body>
<f:view>
<h3><h:outputText value="#{msg.greeting}"/></h3>
<h:outputText value="#{msg.namePrompt}"/>
<h:outputText value="#{login.name}" /><br/>
<h:outputText value="#{msg.passPrompt}"/>
<h:outputText value="#{login.pass}" />
<h:outputText value="#{msg.sign}"/>
</f:view>
</body>
</html>
正如前面导航规则指定的,如果用户输入的数据不能成功登录,系统将返回login.jsp页面,该页面中还有一个页面组件的value绑定到login托管Bean的err属性,当登录失败后,login托管Bean的err属性值就不再为空,即可看到如图2.7所示的页面。
图2.7 登录失败页面