1. JSF入门
藉由以下的几个主题,可以大致了解JSF的轮廓与特性,我们来看看网页设计人员与应用程序设计人员各负责什么。
1.1简介JSF
Web应用程序的开发与传统的单机程序开发在本质上存在着太多的差异,Web应用程序开发人员至今不可避免的必须处理 HTTP的细节,而HTTP无状态的 (stateless)本质,与传统应用程序必须维持程序运行过程中的信息有明显的违背,再则Web应用程序面对网站上不同的使用者同时的存取,其执行绪 安全问题以及资料验证、转换处理等问题,又是复杂且难以解决的。
另一方面,本质上是静态的HTML与本质上是动态的应用程序又是一项违背,这造成不可避免的,处理网页设计的美术人员与 程序设计人员,必须被彼此加入至视图组件中的逻辑互相干扰,即便一些视图呈现逻辑以卷标的方式呈现,试图展现对网页设计美术人员的亲切,但它终究必须牵涉 到相关的流程逻辑。
有很多方案试着解决种种的困境,而各自的着眼点各不相同,有的从程序设计人员的角度来解决,有的从网页设计人员的角度来 解决,各种的框架被提出,所造成的是各种不统一的标签与框架,为了促进产能的整合开发环境(IDE)难以整合这些标签与框架,另一方面,开发人员的学习负 担也不断的加重,他们必须一人了解多个角色的工作。
Java Server Faces的提出在试图解决这个问题,它试图在不同的角度上提供网页设计人员、应用程序设计人员、组件开发人员解决方案,让不同技术的人员可以彼此合作又 不互相干扰,它综合了各家厂商现有的技术特点,由Java Community Process(JCP)团队研拟出来的一套标准,并在2004年三月发表了Java ServerFaces 1.0实作成果。
从网页设计人员的角度来看,Java Server Faces提供了一套像是新版本的HTML标签,但它不是静态的,而是动态的,可以与后端的动态程序结合,但网页设计人员不需要理会后端的动态部份,网页 设计人员甚至不太需要接触JSTL这类的卷标,也可以动态的展现数据(像是动态的查询表格内容),Java Server Faces提供标准的标签,这可以与网页编辑程序结合在一起,另一方面,Java Server Faces也允许您自订标签。
从应用程序设计人员的角度来看,Java Server Faces提供一个与传统应用程序开发相类似的模型(当然因某些本质上的差异,模型还是稍有不同),他们可以基于事件驱动来开发程序,不必关切HTTP的 处理细节,如果必须处理一些视觉组件的属性的话,他们也可以直接在整合开发环境上拖拉这些组件,点选设定组件的属性,Java Server Faces甚至还为应用程序设计人员处理了对象与字符串(HTTP传送本质上就是字符串)间不匹配的转换问题。
从UI组件开发人员的角度来看,他们可以设计通用的UI组件,让应用程序的开发产能提高,就如同在设计Swing组件等,UI开发人员可以独立开发,只要定义好相关的属性选项来调整细节,而不用受到网页设计人员或应用程序设计人员的干扰。
三个角色的知识领域原则上可以互不干扰,根据您的角色,您只要了解其中一个知识领域,就可以运用Java Server Faces,其它角色的知识领域您可以不用了解太多细节。
当然,就其中一个角色单独来看,Java Server Faces隐藏了许多细节,若要全盘了解,其实Java Server Faces是复杂的,每一个处理的环境都值得深入探讨,所以学习Java ServerFaces时,您要选择的是通盘了解,还是从使用的角度来了解,这就决定了您学习时所要花费的心力。
要使用JSF,首先您要先取得Java Server Faces参考实作(Java Server Faces Reference Implementation),在将来,JSF会与Container整合在一起,届时您只要下载支持的Container,就可以使用JSF的功能。
请至 JSF 官方网站的下载区下 载参考实作,在下载压缩档并解压缩之后,将其 lib 目录下的 jar 档案复制至您的Web应用程序的/WEB-INF/lib目录下,另外您还需要 jstl.jar 与 standard.jar 档案,这些档案您可以在sample目录下,解压缩当中的一个范例,在它的/WEB-INF/lib目录下找到,将之一并复制至您的Web应用程序的 /WEB-INF/lib目录下,您总共需要以下的档案:
* jsf-impl.jar
*jsf-api.jar
*commons-digester.jar
*commons-collections.jar
*commons-beanutils.jar
*jstl.jar
* standard.jar
接下来配置Web应用程序的web.xml,使用JSF时,所有的请求都透过Faces Servlet来处理,您可以如下定义:
web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
<description> JSF Demo </description>
<display-name>JSF Demo</display-name>
<servlet> <servlet-name> Faces Servlet </servlet-name>
<servlet-class> javax.faces.webapp.FacesServlet </servlet-class>
<load-on-startup>1</load-on-startup> </servlet>
<servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.faces</url-pattern> </servlet-mapping>
<welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list>
</web-app> |
在上面的定义中,我们将所有.faces的请求交由FaceServlet来处理,FaceServlet会唤起相对的.jsp网页,例如请求是/index.faces的话,则实际上会唤起/index.jsp网页,完成以上的配置,您就可以开始使用JSF了。
1.2第一个JSF程序
现在可以开发一个简单的程序了,我们将设计一个简单的登入程序,使用者送出名称,之后由程序显示使用者名称及欢迎讯息。
程序开发人员
先看看应用程序开发人员要作些什么事,我们撰写一个简单的JavaBean:
UserBean.java
package onlyfun.caterpillar;
public class UserBean {
private String name;
public void setName(String name) { this.name = name; }
public String getName() { return name; } } |
这个Bean将储存使用者的名称,编译好之后放置在/WEB-INF/classes下。接下来设计页面流程,我们将先 显示一个登入网页/pages/index.jsp,使用者填入名称并送出窗体,之后在/pages/welcome.jsp中显示Bean中的使用者名 称与欢迎讯息。为了让JSF知道我们所设计的Bean以及页面流程,我们定义一个/WEB-INF/faces-config.xml:
faces-config.xml
<?xml version="1.0"?> <!DOCTYPE faces-config PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN" "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<faces-config> <navigation-rule> <from-view-id>/pages/index.jsp</from-view-id> <navigation-case> <from-outcome>login</from-outcome> <to-view-id>/pages/welcome.jsp</to-view-id> </navigation-case> </navigation-rule>
<managed-bean> <managed-bean-name>user</managed-bean-name> <managed-bean-class> onlyfun.caterpillar.UserBean </managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> </faces-config> |
在<navigation-rule>中,我们定义了页面流程,当请求来自<from-view- id>中指定的页面,并且指定了<navigation-case>中的<from-outcome>为login时,则 会将请求导向至<to-view-id>所指定的页面。在<managed-bean>中我们可以统一管理我们的Bean,我们 设定Bean对象的存活范围是session,也就是使用者开启浏览器与程序互动过程中都存活。接下来要告诉网页设计人员的信息是,他们可以使用的 Bean名称,即<managed-bean-name>中设定的名称,以及上面所定义的页面流程。
网页设计人员
首先网页设计人员撰写index.jsp网页:
index.jsp
<%@taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html;charset=Big5"%> <html> <head> <title>第一个JSF程序</title> </head> <body> <f:view> <h:form> <h3>请输入您的名称</h3> 名称: <h:inputText value="#{user.name}"/><p> <h:commandButton value="送出" action="login"/> </h:form> </f:view> </body> </html> |
我们使用了JSF的core与html标签库,core是有关于UI组件的处理,而html则是有关于HTML的进阶标签。<f:view>与<html>有类似的作用,当您要开始使用JSF组件时,这些组件一定要在<f: view>与</f:view>之 间,就如同使用HTML时,所有的标签一定要在<html>与< /html>之间。html卷标库中几乎都是与HTML卷标相关的进阶卷标,<h:form>会产生一个窗体,我们使用<h: inputText>来显示user这个Bean对象的name属性,而<h:commandButton>会产生一个提交按钮,我们 在action属性中指定将根据之前定义的login页面流程中前往welcome.jsp页面。网页设计人员不必理会窗体传送之后要作些什么,他只要设 计好欢迎页面就好了:
welcome.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html;charset=Big5"%> <html> <head> <title>第一个JSF程序</title> </head> <body> <f:view> <h:outputText value="#{user.name}"/> 您好! <h3>欢迎使用 Java Server Faces!</h3> </f:view> </body> </html> |
这个页面没什么需要解释的了,如您所看到的,在网页上没有程序逻辑,网页设计人员所作的就是遵照页面流程,使用相关名称 取出数据,而不用担心实际上程序是如何运作的。接下来启动Container,连接上您的应用程序网址,例 如:http://localhost:8080/jsfDemo/pages/index.faces,填入名称并送出窗体,您的欢迎页面就会显示了。
1.3简单的导航 Navigation
在第一个JSF程序中,我们简单的定义了页面的流程由index.jsp 到 welcome.jsp,接下来我们扩充程序,让它可以根据使用者输入的名称与密码是否正确,决定要显示欢迎讯息或是将使用者送回原页面进行重新登入。首先我们修改一下UserBean:
UserBean.java
package onlyfun.caterpillar;
public class UserBean {
private String name; private String password; private String errMessage;
public void setName(String name) { this.name = name; }
public String getName() { return name; }
public void setPassword(String password) { this.password = password; }
public String getPassword() { return password; }
public void setErrMessage(String errMessage) { this.errMessage = errMessage; }
public String getErrMessage() { return errMessage; }
public String verify() { if(!name.equals("justin") ||!password.equals("123456")) { errMessage = "名称或密码错误"; return "failure"; } else { return "success"; } } } |
在UserBean中,我们增加了密码与错误讯息属性,在verify()方法中,我们检查使用者名称与密码,它传回一 个字符串,"failure"表示登入错误,并会设定错误讯息,而"success"表示登入正确,这个传回的字符串将决定页面的流程。接下来我们修改一 下faces-config.xml 中的页面流程定义:
faces-config.xml
<?xml version="1.0"?> <!DOCTYPE faces-config PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN" "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<faces-config> <navigation-rule> <from-view-id>/pages/index.jsp</from-view-id> <navigation-case> <from-outcome>success</from-outcome> <to-view-id>/pages/welcome.jsp</to-view-id> </navigation-case> <navigation-case> <from-outcome>failure</from-outcome> <to-view-id>/pages/index.jsp</to-view-id> </navigation-case> </navigation-rule>
<managed-bean> <managed-bean-name>user</managed-bean-name> <managed-bean-class> onlyfun.caterpillar.UserBean </managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> </faces-config>
|
根据上面的定义,当传回的字符串是"success"时,将前往 welcome.jsp,如果是"failure"的话,将送回 index.jsp。接下来告诉网页设计人员Bean名称与相关属性,以及决定页面流程的verify名称,我们修改 index.jsp 如下:
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html;charset=Big5"%> <html> <head> <title>第一个JSF程序</title> </head> <body> <f:view> <h:form> <h3>请输入您的名称</h3> <h:outputText value="#{user.errMessage}"/><p> 名称: <h:inputText value="#{user.name}"/><p> 密码: <h:inputSecret value="#{user.password}"/><p> <h:commandButton value="送出"action="#{user.verify}"/> </h:form> </f:view> </body> </html> |
当要根据verify运行结果来决定页面流程时,action属性中使用JSF Expression Language"#{user.verify}",如此JSF就知道必须根据verify传回的结果来导航页 面。<h:outputText>可以取出指定的Bean之属性值,当使用者因验证错误而被送回原页面时,这个错误讯息就可以显示在页面上。
1.4导航规则设置
在JSF中是根据faces-config.xml中<navigation-rule>设定,以决定在符合的条件成立时,该连结至哪一个页面,一个基本的设定如下:
<navigation-rule> <from-view-id>/pages/index.jsp</from-view-id> <navigation-case> <from-outcome>success</from-outcome> <to-view-id>/pages/welcome.jsp</to-view-id> </navigation-case> <navigation-case> <from-outcome>failure</from-outcome> <to-view-id>/pages/index.jsp</to-view-id> </navigation-case> </navigation-rule> |
对于JSF,每一个视图(View)都有一个独特的识别(identifier),称之为View ID,在JSF中的View ID是从Web应用程序的环境相对路径开始计算,设定时都是以“/”作为开头,如果您请求时的路径是/pages/index.faces,则JSF会将 扩展名改为/pages/index.jsp,以此作为view-id。在<navigation-rule>中的<from- view-id>是个选择性的定义,它规定了来源页面的条件,<navigation-case>中定义各种导览条 件,<from-outcome>定义当窗体结果符合的条件时,各自改导向哪一个目的页面,目的页面是在<to-view- id>中定义。您还可以在<navigation-case>中加入<from-action>,进一步规范窗体结果必须 根据哪一个动作方法(action method),当中是使用 JSF Expression Language 来设定,例如:
<navigation-rule> <from-view-id>/pages/index.jsp</from-view-id> <navigation-case> <from-action>#{user.verify}</from-action> <from-outcome>success</from-outcome> <to-view-id>/pages/welcome.jsp</to-view-id> </navigation-case> </navigation-rule> |
在导航时,预设都是使用forward的方式,您可以在<navigation-case>中加入一个<redirect/>,让JSF发出让浏览器重新导向(redirect)的header,让浏览器主动要求新网页,例如:
<navigation-rule> <from-view-id>/pages/index.jsp</from-view-id> <navigation-case> <from-outcome>success</from-outcome> <to-view-id>/pages/welcome.jsp</to-view-id> <redirect/> </navigation-case> </navigation-rule> |
您的来源网页可能是某个特定模块,例如在/admin/下的页面,您可以在<from-view-id>中使用wildcards(通配符),也就是使用“*”字符,例如:
<navigation-rule> <from-view-id>/admin/*</from-view-id> <navigation-case> <from-action>#{user.verify}</from-action> <from-outcome>success</from-outcome> <to-view-id>/pages/welcome.jsp</to-view-id> </navigation-case> </navigation-rule> |
在上面的设定中,只要来源网页是从/admin来的,都可以开始测试接下来的<navigation-case>。
<from-view-id>如果没有设定,表示来源网页不作限制,您也可以使用 * 显式的在定义档中表明,例如:
<navigation-rule> <from-view-id>/*</from-view-id> </navigation-rule> |
或者是这样:
<navigation-rule> <from-view-id>*</from-view-id> </navigation-rule> |
1.5 JSF Expression Language
JSF Expression Language搭配JSF标签来使用,是用来存取数据对象的一个简易语言。JSF EL(ExpressionLanguage)是以“#”开始,将变量或表达式放置在Unknown macro:“{”与“}”之间,例如:
#{someBeanName}
变量名称可以是faces-config.xml中定义的名称,如果是Bean的话,可以透过使用“.” 运算子来存取它的属性,例如:
<f:view> <h:outputText value="#{userBean.name}"/> </f:view> |
在JSF卷标的属性上,“"”与“"”(或“'”与“'”)之间如果含有EL,则会加以运算,您也可以这么使用它:
<f:view> 名称,年龄:<h:outputTextvalue="#{userBean.name}, #{userBean.age}"/> </f:view> |
一个执行的结果可能是这样显示的:名称,年龄:Justin, 29
EL的变量名也可以程序执行过程中所宣告的名称,或是JSF EL预设的隐含对象,例如下面的程序使用param隐含对象来取得使用者输入的参数:
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html; charset=Big5"%> <html> <head> <title></title> </head> <body> <f:view> <b> 您好, <h:outputText value="#{param.name}"/> </b> </f:view> </body> </html> |
param是JSF EL预设的隐含对象变量,它代表request所有参数的集合,实际是一个java.util.Map型态对象,JSF所提供的隐含对象,大致上对应于 JSP隐含物件,不过JSF隐含对象移除了pageScope与pageContext,而增加了facesContext与view,它们分别对应于 javax.faces.context.FacesContext与javax.faces.component.UIViewRoot。
对于Map型态对象,我们可以使用“.”运算子指定key值来取出对应的value,也可以使用“[”与“]”来指定,例如:
<f:view> <b> 您好, <h:outputText value="#{param['name']}"/> </b> </f:view> |
在“[”与“]”之间,也可以放置其它的变量值,例如:
<f:view> <h:outputText value="#{someBean.someMap[user.name]}"/> </f:view> |
如果变量是List型态或数组的话,则可以在“[]”中指定索引,例如:
<f:view> <h:outputText value="#{someBean.someList[0]}"/> <h:outputText value="#{someBean.someArray[1]}"/> <h:outputTextvalue="#{someBean.someListOrArray[user.age]}"/> </f:view> |
您也可以指定字面常数,对于true、false、字符串、数字,JSF EL会尝试进行转换,例如:
<h:outputText value="#{true}"/> <h:outputText value="#{'This is a test'}"/> |
如果要输出字符串,必须以单引号 ' 或双自变量"括住,如此才不会被认为是变量名称。在宣告变量名称时,要留意不可与JSF的保留字或关键词同名,例如不可取以下这些名称:
true false null div mod and or not eq ne lt gtle ge instanceof empty
使用EL,您可以直接实行一些算术运算、逻辑运算与关系运算,其使用就如同在一般常见的程序语言中之运算。算术运算子有:加法 (+), 减法 (-), 乘法 (*), 除法 (/ or div) 与余除 (% or mod) 。下面是算术运算的一些例子:
表达式 |
结果 |
#{1} |
1 |
#{1 + 2} |
3 |
#{1.2 + 2.3} |
3.5 |
#{1.2E4 + 1.4} |
12001.4 |
#{-4 - 2} |
-6 |
#{21 * 2} |
42 |
#{3/4} |
0.75 |
#{3 div 4} |
0.75,除法 |
#{3/0} |
Infinity |
#{10%4} |
2 |
#{10 mod 4} |
2,也是余除 |
#{(1==2) ? 3 : 4} |
4 |
如同在Java语法一样 (expression ? result1 : result2)是个三元运算,expression为true显示result1,false显示result2。逻辑运算 有:and(或&&)、or(或!!)、not(或!)。一些例子为:
表达式 |
结果 |
#{true and false} |
false |
#{true or false} |
true |
#{not true} |
false |
关系运算有:小于Less-than (< or lt)、大于Greater-than (> or gt)、小于或等于Less-than-or-equal (<= or le)、大于或等于Greater-than-or-equal (>= or ge)、等于Equal (== or eq)、不等于Not Equal (!= or ne),由英文名称可以得到lt、gt等运算子之缩写词,以下是Tomcat的一些例子: