JSF 组件模型
JSF 组件模型与 AWT GUI 组件模型类似。它有事件和属性,就像 Swing 组件模型一样。它也有包含组件的容器,容器也是组件,也可以由其他容器包含。从理论上说,JSF 组件模型分离自 HTML 和 JSP。JSF 自带的标准组件集里面有 JSP 绑定,可以生成 HTML 渲染。
JSF 组件的示例包括日历输入组件和 HTML 富文本输入组件。您可能从来没时间去编写这样的组件,但是如果它们已经存在,那会如何呢?通过把常用功能变成商品,组件模型降低了向 Web 应用程序添加更多功能的门槛。
组件的功能通常围绕着两个动作:解码和编码数据。解码 是把进入的请求参数转换成组件的值的过程。编码 是把组件的当前值转换成对应的标记(也就是 HTML)的过程。
JSF 框架提供了两个选项用于编码和解码数据。使用直接实现 方式,组件自己实现解码和编码。使用委托实现 方式,组件委托渲染器进行编码和解码。如果选择委托实现,可以把组件与不同的渲染器关联,会在页面上以不同的方式渲染组件;例如多选列表框和一列复选框。
因此,JSF 组件由两部分构成:组件和渲染器。JSF 组件 类定义 UI 组件的状态和行为;渲染器 定义如何从请求读取组件、如何显示组件 —— 通常通过 HTML 渲染。渲染器把组件的值转换成适当的标记。事件排队和性能验证发生在组件内部。
在图 1 中可以看到数据编码和解码出现在 JSF 生命周期中的什么阶段(到现在,我希望您已经熟悉 JSF 生命周期了)。
提示!
在许多情况下,可以在保持组件本身不变的情况下,通过改变渲染而简化开发过程。在这些情况下,可以编写定制渲染器而不是定制组件。
更多组件概念
所有 JSF 组件的基类是 UIComponent
。在开发自己的组件时,需要继承 UIComponentBase
,它扩展了 UIComponent
并提供了 UIComponent
中所有抽象方法的默认实现。
组件拥有双亲和标识符。每个组件都关联着一个组件类型,组件类型用于在 face 的上下文配置文件(faces-config.xml)中登记组件。可以用 JSF-EL (表达式语言)把 JSF 组件绑定到受管理的 bean 属性。可以把表达式关联到组件上的任何属性,这样就允许用 JSF-EL 设置组件的属性值。在创建使用 JSF-EL 绑定的组件属性时,需要创建值绑定表达式。在调用绑定属性的 getter 方法时,除非 setter 方法已经设置了值,否则 getter 方法必须用值绑定获得值。
组件可以作为 ValueHolder
或 EditableValueHolder
。ValueHolder
与一个或多个 Validator
和 Converter
相关联;所以 JSF UI 组件也与 Validator
和 Converter
关联(请参阅 参考资料 获得更多关于 JSF 验证和转换的内容。)
像表单字段组件这样的组件拥有一个 ValueBinding
,它必须绑定到 JavaBean 的读写属性。组件可以调用 getParent
方法访问它们的双亲,也可以调用 getChildren
方法访问它们的子女。组件也可以有 facet 组件,facet 组件是当前组件的子组件,可以调用 getFacets
方法访问它,这个方法返回一个映射。Facets 是著名的子组件。
这里描述的许多组件的概念将会是接下来展示的示例的一部分,所以请记住它们!
JSF 样式的 Hello World!
我们用一个又好又容易的示例来开始 JSF 组件的开发:我将展示如何渲染 Label 标记(示例:<label>Form Test</label>
)。
下面是我要采取的步骤:
- 扩展 UIComponent
- 创建一个类,扩展
UIComponent
- 保存组件状态
- 用 faces-config.xml 登记组件
- 创建一个类,扩展
- 定义渲染器或者内联地实现它
- 覆盖 encode
- 覆盖 decode
- 用 faces-config.xml 登记渲染器
- 创建定制标记,继承 UIComponentTag
- 返回渲染器类型
- 返回组件类型
- 设置可能使用 JSF 表达式的属性
Label 示例将演示 JSF 组件开发的以下方面:
- 创建组件
- 直接实现渲染器
- 编码输出
- 把定制标记与组件关联
返回 图 1,可以看到在这个示例中会有两个生命周期属性在活动。它们是 Apply Request Value 和 Render Response。
在图 2 中,可以看到在 JSP 中如何使用 Label 标记的(<label>Form Test</label>
)。
第 1 步:扩展 UIComponent
第一步是创建一个组件,继承 UIOutput
,后者是 UIComponent
的子类。 除了继承这个类之外,我还添加了组件将会显示的 label 属性,如清单 1 所示:
清单 1. 继承 UIComponent 并添加 label
import java.io.IOException; import javax.faces.component.UIOutput; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; public class LabelComponent extends UIOutput{ private String label; public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } ...
接下来要做的是保存组件状态。JSF 通常通过会话、隐藏表单字段、cookies 等进行实际的存储和状态管理。(这通常是用户配置的设置)。要保存组件状态,需要覆盖组件的 saveState
和 restoreState
方法,如清单 2 所示:
清单 2. 保存组件状态
@Override public Object saveState(FacesContext context) { Object values[] = new Object[2]; values[0] = super.saveState(context); values[1] = label; return ((Object) (values)); } @Override public void restoreState(FacesContext context, Object state) { Object values[] = (Object[])state; super.restoreState(context, values[0]); label = (String)values[1]; }
可以注意到,我使用的是 JDK 1.5。我对编译器进行了设置,所以我必须指定 override 注释,以便指明哪些方法要覆盖基类的方法。这样做可以更容易地标识出 JSF 的钩子在哪。
创建组件的最后一步是用 faces-config.xml 登记它,如下所示:
<faces-config> <component> <component-type>simple.Label</component-type> <component-class> arcmind.simple.LabelComponent </component-class> </component> ...
第 2 步:定义渲染器
下面要做的是内联地定义渲染器的功能。稍后我会介绍如何创建独立的渲染器。现在,先从编码 Label 组件的输出、显示 label 开始,如清单 3 所示:
清单 3. 编码组件的输出
public class LabelComponent extends UIOutput{ ... public void encodeBegin(FacesContext context) throws IOException { ResponseWriter writer = context.getResponseWriter(); writer.startElement("label", this); writer.write(label); writer.endElement("label"); writer.flush(); } ... }
注意,响应写入器(javax.faces.context.ResponseWriter
)可以容易地处理 HTML 这样的标记语言。清单 3 的代码输出 <label> 元素体内的 label 的值。
下面显示的 family 属性用来把 Label 组件与渲染器关联。虽然目前 Label 组件还不需要这个属性(因为还没有独立的渲染器),但是在这篇文章后面,在介绍如何创建独立渲染器的时候,会需要它。
public class LabelComponent extends UIOutput{ ... public String getFamily(){ return "simple.Label"; } ... }
插曲:研究 JSF-RI
如果正在使用来自 Sun Microsystems 的 JSF 参考实现(不是 MyFaces 实现),那么就不得不在组件创建代码中添加下面一段:
public void encodeEnd(FacesContext context) throws IOException { return; } public void decode(FacesContext context) { return; }
Sun 的 JSF RI 期望,在组件没有渲染器的时候,渲染器会发送一个空指针异常。MyFaces 实现不要求处理这个需求,但是在代码中包含以上方法依然是个好主意,这样组件既可以在 MyFaces 环境中工作也可以在 JSF RI 环境中工作了。
MyFaces 更好!
如果正在使用 Sun JSF RI 或其他替代品,那么请帮自己一个忙,转到 MyFaces。虽然 MyFaces 不总是 更好的实现,但是目前它是。它的错误消息要比 Sun JSF RI 的好,而这个框架相比之下更严格。
第 3 步:创建定制标记
JSF 组件不是天生绑定到 JSP 上的。要连接起 JSP 世界和 JSF 世界,需要能够返回组件类型的定制标记(然后在 faces-context 文件中登记)和渲染器,如图 3 所示。
注意,由于没有独立的渲染器,所以可以给 getRendererType()
返回 null 值。还请注意,必须已经把 label
属性的值从定制标记设置到组件上,如下所示:
[LabelTag.java] public class LabelTag extends UIComponentTag { … protected void setProperties(UIComponent component) { /* you have to call the super class */ super.setProperties(component); ((LabelComponent)component).setLabel(label); }
记住,Tag
设置从 JSP 到 Label 组件的绑定,如图 4 所示。
现在要做的全部工作就是创建一个 TLD(标记库描述符)文件,以登记定制标记,如清单 4 所示:
清单 4. 登记定制标记
[arcmind.tld] <taglib> <tlib-version>0.03</tlib-version> <jsp-version>1.2</jsp-version> <short-name>arcmind</short-name> <uri>http://arcmind.com/jsf/component/tags</uri> <description>ArcMind tags</description> <tag> <name>slabel</name> <tag-class>arcmind.simple.LabelTag</tag-class> <attribute> <name>label</name> <description>The value of the label</description> </attribute> </tag> ...
一旦定义了 TLD 文件,就可以开始在 JSP 中使用标记了,如下面示例所示:
[test.jsp] <%@ taglib prefix="arcmind" uri="http://arcmind.com/jsf/component/tags" %> ... <arcmind:slabel label="Form Test"/>
现在就可以了 —— 开发一个简单的 JSP 组件不需要更多了。但是如果想创建稍微复杂一些的组件,针对更复杂的使用场景时该怎么办?请继续往下看(Web的开发就是组件的开发 ,JSF的组件开发实例(下))。
http://hi.baidu.com/rover828/blog/item/9ea6ab4ba48d5ef483025ce3.html