1. 结构:
a) 结构图:
b) 说明:JSF以MVC模式为基础,与Struts不同,JSF的目标是希望以一个与Swing相类似的方式来开发网页,因此,从JSF的结构图当中,他的核心概念不是页面,而是控件树,也就是说,当用户提交一个请求时,JSF会先将页面上的组件先转换为与Swing当中类似的,由容器和控件组成的控件树,然后数据和事件被设置到对应的控件上,然后以一种与Swing类似的方式,来处理后续的请求。控件树是整个JSF的核心,所有其他的一切一切都是围绕着这棵控件树展开的。
2. 生命周期:
a) 周期图:
b) 说明:
b) 说明:
b) 说明:
i. Restore View:JSF的处理核心是控件树,他会先将页面上所声明的控件转换为一棵控件树,后续的操作将在这颗控件树上进行。为了提高性能,系统会为之前生成的控件树提供缓存。Restore View的工作就是在缓存当中查找是否存在之前已经生成好的控件树,如果没有,则根据页面的内容,重新生成。
ii. Apply Request Values:把请求当中的数据设置到控件树当中对应的控件当中去。
iii. Process Validations:如果某一控件有配置Validator,则这些Validator将对刚设置的数据的正确性和合法性进行验证。
iv. Update Model Values:控件树上的控件更新其底层所对应的模型。
v. Invoke Application:对产生的事件进行分发。
vi. Render Response:构建作为响应的控件树。
3. UI:
a) 结构图:
b) 控件:
i. 说明:JSF通过标签库,提供了一些主要控件的实现。包括标签,文本框,单选框,列表等。由于JSF使用一种类似于UI的方式来组织组件,所以,除了基本的组件以外,还提供了一些用于布局的容器,例如面板等。在这里有一个要注意的地方就是,一般情况下,页面的内容应该放到JSF提供的view标签里面。
ii. 代码:
<%@page contentType="text/html;charset=gb2312"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<html>
<head><title>Test</title></head>
<body>
<f:view>
<h:form>
<h:panelGrid columns="1">
<h:outputLabel>
<h:outputText value="User ID"/>
</h:outputLabel>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
c) 事件处理:
i. 说明:与Struts不同,由于JSF使用以控件树为中心的方式来处理请求,所以,她提供了一种额外的类似Swing的,事件处理的方式来处理用户的输入事件。JSF提供了两种事件类型,ActionEvent,用于处理命令和ValueChangeEvent,用于处理数据更改。
ii. 代码:
模型代码:
package nick;
public class UserActionListener implements ActionListener {
public void processAction(ActionEvent arg0)
throws AbortProcessingException {
FacesContext context = FacesContext.getCurrentInstance();
ValueBinding binding = Util.getValueBinding("#{user}");
User user = (User) binding.getValue(context);
String id = user.getId();
}
}
页面代码:
<%@page contentType="text/html;charset=gb2312" import="java.util.*"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<html>
<head><title>Test</title></head>
<body>
<f:view>
<h:form>
<h:panelGrid columns="2">
<h:outputLabel for="id">
<h:outputText value="User ID"/>
</h:outputLabel>
<h:commandButton id="regist" value="注册">
<f:actionListener type="nick.UserActionListener"/>
</h:commandButton>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
注:通过嵌套actionListener标签,我们可以为一个控件注册监视器。
4. 数据绑定:
a) 说明:数据绑定要解决的问题就是如何把模型中的值,绑定到页面的控件上。在JSF当中这可以通过JSF所提供的配置文件来完成。
b) 代码:
配置文件:
<faces-config>
<managed-bean>
<managed-bean-name> user </managed-bean-name>
<managed-bean-class> nick.User </managed-bean-class>
<managed-bean-scope> session </managed-bean-scope>
</managed-bean>
</faces-config>
模型代码:
package nick;
public class User {
private String id = "Nick";
public void setId(String id) {
this.id = id;
}
public String getId() {
return this.id;
}
}
页面代码:
<%@page contentType="text/html;charset=gb2312"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<html>
<head><title>Test</title></head>
<body>
<f:view>
<h:form>
<h:panelGrid columns="2">
<h:outputLabel for="id">
<h:outputText value="User ID"/>
</h:outputLabel>
<h:inputText id="id" value="#{user.id}"/>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
注:通过配置文件,我们把nick.User类绑定到名称user上,然后页面的代码就可以直接使用#{user.xxx}来引用User这个类中的各个字段。
5. 页面流:
a) 页面到控制器:
i. 说明:JSF通过使用方法绑定的方式来定义从页面到控制器的跳转,和数据绑定相同,为了能够正确找到被绑定方法所在的类,我们需要首先在配置文件当中声明managed-bean,然后通过设置控件的action属性,定义页面到控制器的跳转逻辑。
ii. 代码:
配置文件:
<faces-config>
<managed-bean>
<managed-bean-name> user </managed-bean-name>
<managed-bean-class> nick.User </managed-bean-class>
<managed-bean-scope> session </managed-bean-scope>
</managed-bean>
</faces-config>
模型代码:
package nick;
public class User {
public String regist() {
return "regist";
}
public String login() {
return "login";
}
}
页面代码:
<%@page contentType="text/html;charset=gb2312"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<html>
<head><title>Test</title></head>
<body>
<f:view>
<h:form>
<h:panelGrid columns="2">
<h:commandButton id="regist"
action="#{user.regist}" value="注册"/>
<h:commandButton id="login"
action="#{user.login}" value="登陆"/>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
注:上述的页面代码,把注册按钮的动作绑定到User类的regist()方法,把登陆按钮的动作绑定到User类的login()方法。因此,当这两个按钮被点击时,对应的方法将被调用,用于实现页面流的方法,必须声明为public,而且她不接受参数,且返回值必须为String。
b) 控制器到页面:
i. 说明:JSF通过名称绑定的方式,来定义从控制器到页面的跳转。为了实现从控制器到页面的跳转,我们需要在配置文件当中定义一些<navigation-rule>,这些rule主要定义了怎么根据上述action标签所绑定的方法的返回值来查找下一页面。
ii. 代码:
配置文件:
<faces-config>
<managed-bean>
<managed-bean-name> user </managed-bean-name>
<managed-bean-class> nick.User </managed-bean-class>
<managed-bean-scope> session </managed-bean-scope>
</managed-bean>
<navigation-rule>
<from-view-id>/index.jsp</from-view-id>
<navigation-case>
<from-outcome>regist</from-outcome>
<to-view-id>/regist.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>login</from-outcome>
<to-view-id>/login.jsp</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
模型代码:
package nick;
public class User {
public String regist() {
return "regist";
}
public String login() {
return "login";
}
}
注:上述的配置文件定义了一个<navigation-rule>,该rule指明了如果“/index.jsp”页面通过她内部的某个控件的action属性发生了跳转,那么当该跳转方法的返回值为字符串“regist”时,则页面将跳转到对应的“/regist.jsp”中,同理,如果返回值为“login”,则页面将跳转到“/login.jsp”。
6. 数据传输:
a) 页面到控制器:
i. 说明:在JSF的页面代码当中,通过数据绑定,我们把控件的value值,与某个后台的数据bean关联起来。而在前述的生命周期部分,我们看到,当一个JSF请求到达时,他需要经历Restore View,Apply Request Value等步骤,而Apply Request Value部分的工作,就是把请求当中的值绑定到这个后台的bean之中,因此,我们不需要考虑页面中的Form值如何传入到后台的bean当中。
进一步,如果录入控件的value属性,和命令控件的action属性都是绑定在同一个bean上的话,那么在页面跳转时,我们可以直接访问到bean的属性值。但是为了不污染模型,和实现控制与模型的分离,一般情况下,我们需要把输入控件的value值绑定到数据bean,而把命令控件的action值绑定到控制bean,由于两个bean不是同一个,所以,控制bean需要一种方法来获取数据bean中的属性值。
ii. 代码:
配置文件:
<faces-config>
<managed-bean>
<managed-bean-name> user </managed-bean-name>
<managed-bean-class> nick.User </managed-bean-class>
<managed-bean-scope> session </managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name> action </managed-bean-name>
<managed-bean-class> nick.Action </managed-bean-class>
<managed-bean-scope> session </managed-bean-scope>
</managed-bean>
</faces-config>
模型代码:
package nick;
public class User {
private String id = "Nick";
public void setId(String id) {
this.id = id;
}
public String getId() {
return this.id;
}
}
package nick;
public class Action {
public String regist() {
ValueBinding binding = Util.getValueBinding("#{user}");
User user = (User)
binding.getValue(FacesContext.getCurrentInstance());
…
return "regist";
}
}
页面代码:
<%@page contentType="text/html;charset=gb2312"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<html>
<head><title>Test</title></head>
<body>
<f:view>
<h:form>
<h:panelGrid columns="2">
<h:outputLabel for="id">
<h:outputText value="User ID"/>
</h:outputLabel>
<h:inputText id="id" value="#{user.id}"/>
<h:commandButton id="regist"
action="#{action.regist}" value="注册"/>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
注:页面代码当中把输入控件的value绑定到了user的id上,把命令控件的action值绑
定到action的regist上。当用户点击登陆按钮时,action的regist()方法将会被调用,
而在该方法内部,为了获取之前页面中的信息,我们可以使用ValueBinding类。该类使用
的数据绑定表达式与页面中的类似。
b) 控制器到页面:
i. 说明:通过之前的叙述,我们可以发现,当我们需要把数据从控制器传到页面时,我们同样可以使用数据绑定的方式,因为,当我们通过数据绑定表达式,获取到某个页面当中所使用的模型实例时,便可以在该实例上直接调用对应的set()方法,来设定所希望的值。
7. 插件功能:
a) Converter(转换):
i. 说明:当模型中的某个字段需要在页面上显示时,她需要先被转换为字符串类型;而用户在页面输入的字符串值,在传到模型中时也需要根据模型对应字段的类型,进行一个转换。另外,由于国际化的要去,模型中的值在不同的地区会有不同的表示方式。为了解决以上这些问题,JSF提供了Converter的实现,她主要做的事情就是根据所在的地区,对页面数据和模型数据进行双向的转换。
ii. 代码:
模型代码:
package nick;
public class User {
private Date date = new Date();
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
页面代码:
<%@page contentType="text/html;charset=gb2312"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<html>
<head><title>Test</title></head>
<body>
<f:view>
<h:form>
<h:panelGrid columns="3">
<h:outputLabel for="date">
<h:outputText value="date"/>
</h:outputLabel>
<h:inputText id="date" value="#{user.date}">
<f:convertDateTime pattern="M/d/yyyy"/>
</h:inputText>
<h:message for="date"/>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
注:通过嵌套的convertXXX标签,我们可以为控件配置用于转换的转换器。我们可以使用两种方式来注册转换器,一是通过控件的convert属性,另外一种就是通过嵌套的convertXXX标签。如果在转换的时候发生错误,那么JSF将跳过转换以后的步骤,而直接跳到Render Response步骤,生成响应,并在FacesContext里添加一个出错的Message,该Message的内容可以通过message标签进行显示。
b) Validate(验证):
i. 说明:在数据被交付后台处理以前,我们可以通过验证器,来验证输入的数据是否合法,这包括数值的大小,或者是字符串的长度等。使用验证器的好处就是我们可以把验证的代码从控制代码中单独出来,以便管理和重用。
ii. 代码:
页面代码:
<%@page contentType="text/html;charset=gb2312"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<html>
<head><title>Test</title></head>
<body>
<f:view>
<h:form>
<h:panelGrid columns="3">
<h:outputLabel for="id">
<h:outputText value="User ID"/>
</h:outputLabel>
<h:inputText id="id" value="#{user.id}">
<f:validateLength minimum="3" maximum="6"/>
</h:inputText>
<h:message for="id"/>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
注:通过嵌套的validateXXX标签我们可以为一个控件注册一个验证器,与转换器不同,我们可以为一个控件注册多个验证器,以进行复杂的验证。注册验证器的方法也有两个,一是通过控件的validator属性,另外一种就是通过嵌套的validateXXX标签。与转换器类似,当验证失败时,失败的原因也会以Message的形式被放到FacesContext内,在页面内通过message标签可以对错误信息进行显示。
8. 国际化支持
a) 说明:为了提供对国际化的支持,JSF使用了与Struts类似的通过资源文件的形式,从外部获取需要显示的内容。
b) 代码:
配置文件:
<faces-config>
<application>
<message-bundle>nick.Messages</message-bundle>
<locale-config>
<default-locale>cn</default-locale>
<supported-locale>en</supported-locale>
</locale-config>
</application>
</faces-config>
页面代码:
<%@page contentType="text/html;charset=gb2312"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<html>
<head><title>Test</title></head>
<f:loadBundle basename="nick.Messages" var="message"/>
<body>
<f:view>
<h:form>
<h:panelGrid columns="3">
<h:outputLabel for="id">
<h:outputText value="#{message.id}"/>
</h:outputLabel>
<h:inputText id="id" value="#{user.id}"/>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
注:如果我们需要考虑国际化的问题时,那么我们需要为同一个资源提供多个地区的资源文件实现。通过在配置文件中声明message-bundle,我们可以注册资源文件的多个版本,以上面为例,她注册了两个资源文件nick.Messages_cn.properties和nickcen.Messages_en.properties分别对应于中国和英国地区,当页面当中通过loadBundle标签读取资源时,她就会根据所在的区域,去查找对应的文件。当然,所有资源文件都应该使用java所提供的native2ascii工具,进行一下预处理。
9. JSF定制:
a) 说明:该部分主要展示如何对JSF提供的转换器,验证器和控件进行定制,以扩展JSF的功能。
b) 配置文件定制:
i. 说明:JSF提供了比Struts更多的定制特性,包括用于实例化前端控制FacesServelt的faces-context-factory,用于实例化控件绘画RenderKit的render-kit-factory,进行声明周期管理的lifecyle-factory等。
ii. 代码:
配置文件:
<faces-config>
<factory>
<application-factory>…</application-factory>
<faces-context-factory>…</faces-context-factory>
<lifecycle-factory>…</lifecycle-factory>
<render-kit-factory>…</render-kit-factory>
</factory>
</faces-config>
c) 转换器定制:
i. 说明:通过扩展JSF提供的Converter接口,我们可以定义自己的转换器。而为了能在页面上使用自定义的转换器,我们需要在配置文件中,对转换器进行注册。
ii. 代码:
配置文件:
<faces-config>
<converter>
<converter-id>nameConverter</converter-id>
<converter-class>nick.NameConverter</converter-class>
</converter>
</faces-config>
模型文件:
package nick;
public class NameConverter implements Converter {
public Object getAsObject(FacesContext ar0, UIComponent ar1, String ar2) {
return ar2.toLowerCase();
}
public String getAsString(FacesContext ar0, UIComponent ar1, Object ar2) {
retrun ((String) arg2).toUpperCase();
}
}
页面文件:
<%@page contentType="text/html;charset=gb2312"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<html>
<head><title>Test</title></head>
<f:loadBundle basename="nick.Messages" var="message"/>
<body>
<f:view>
<h:form>
<h:panelGrid columns="3">
<h:outputLabel for="id">
<h:outputText value="#{message.id}"/>
</h:outputLabel>
<h:inputText id="id"
value="#{user.id}" immediate="true">
<f:converter converterId="nameConverter"/>
</h:inputText>
<h:message for="id"/>
<h:commandButton
action="#{action.regist}" value="Regist"/>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
注:当页面数据转换为后台数据时,Converter的getAsObject()方法会被调用。而当需要把后台数据显示到页面中时,getAsString()方法会被调用。这里,我们注意到,我们的Converter是不能带任何配置属性的,为了让我们的Converter能够在页面中增加属性,我们需要进一步的定义自己的标签。除了可以根据converter-id来注册转换器以外,我们还可以使用converter-for-class标签来为某一特定的Java类型注册转换器。如果在转换过程中发生错误的话,那么我们可以通过抛出ConverterException来停止后续的工作。
d) 验证器定制:
i. 说明:通过扩展Validator接口,我们可以定义自己的验证器。而为了能在页面上使用自定义的验证器,我们需要在配置文件中,对验证器进行注册。
ii. 代码:
配置文件:
<faces-config>
<validator>
<validator-id>nameValidator</validator-id>
<validator-class>nick.NameValidator</validator-class>
</validator>
</faces-config>
模型文件:
package nick;
public class NameValidator implements Validator {
public void validate(FacesContext arg0, UIComponent arg1, Object arg2)
throws ValidatorException {
String name = (String) arg2;
if (!name.startsWith("nick")) {
throw new ValidatorException(new FacesMessage(
"name must start with nick"));
}
}
}
页面文件:
<%@page contentType="text/html;charset=gb2312"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<html>
<head><title>Test</title></head>
<f:loadBundle basename="nick.Messages" var="message"/>
<body>
<f:view>
<h:form>
<h:panelGrid columns="3">
<h:outputLabel for="id">
<h:outputText value="#{message.id}"/>
</h:outputLabel>
<h:inputText id="id" value="#{user.id}"> <f:validator validatorId="nameValidator"/>
</h:inputText>
<h:message for="id"/>
<h:commandButton
action="#{action.regist}" value="Regist"/>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
注:定制验证器的操作与定制转换器的操作大致相同,如果我们希望通过页面的方式为验证器提供属性的话,那么我们也需要定义额外的标签。
e) 控件定制:
i. 说明:JSF允许用户基于JSF的框架,进行控件的定制,控件定制需要完成以下几部分的工作,1.需要一个控件实现类,为了能让控件在页面上使用,我们的控件还需要在配置文件中,通过component标签进行注册;2.用于控件渲染的Render类;3.用于在页面上使用控件的标签。
f) 控件树定制:
i. 说明:除了可以通过页面标签来声明控件以外,我们可以通过程序的方式来定制控件树,通过调用FacesContext上的getViewRoot()方法,我们可以获得控件树的根引用,通过该引用,我们可以动态的对控件进行增删,或者动态的为某一控件增删事件监听器,和验证器等。
g) Web常量:
i. 说明:JSF提供了一组操作application,context,session,cookies,page等常量的方法。通过调用FacesContext上的getExternalContext()方法,我们可以获得一个对ExternalContext的引用,通过该引用我们可以获取所有我们希望使用的session,cookies等常量的引用,除此以外,我们还能获取request当中的头信息和请求信息等。