JSF自定义组件

cameron6

贡献于2011-08-12

字数:0 关键词: JSF Web框架

JSF自定义组件 原文:http://www.javaworld.com.tw/confluence/pages/viewpage.action?pageId=2630 繁简转换与修订:jnzgy@163.com 声明:为方便简体中文读者阅读,特将原文转换为简体,对一些习惯用语做了替换,同时对部分内容 做了修订。版权归原文作者所有,未经许可,不得用于商业目的。 2006年3月28日 1. JSF 生命周期与组件概述............................................................................................................2 1.1 JSF 生命周期......................................................................................................................2 1.2 概述自定义组件................................................................................................................3 2. 简单实例......................................................................................................................................5 2.1 编码、解码........................................................................................................................5 2.2 组件标签............................................................................................................................8 2.3 使用自定义组件..............................................................................................................12 2.4 自定义 Renderer..............................................................................................................14 JSF 自定义组件 JSF 让您可以自定义组件,每个组件都是可替换的,这使得组件在搭配时更 有弹性,但相对的却使开发组件的过程复杂的多,这里对自定义JSF 组件只是个 入门砖,更多有关自定义组件的细节可得要专书来说明。 1. JSF 生命周期与组件概述 要开发JSF组件,您需要更深入了解JSF的一些处理细节,包括了JSF生命周 期以及JSF框架。 1.1 JSF 生命周期 JSF的每个组件基本上都是可替换的,像是转换器(Converter)、验证器 (Validator)、组件(Component)、绘制器(Renderer)等等,每个组件都可 以替换让JSF在使用时更有弹性,但相对的所付出的就是组件组合时的复杂性, 为此,最基本的,如果您打算自定义一些JSF组件,那么您对于JSF处理请求的每 个阶段必须要有所了解。 下图是JSF处理请求时的每个阶段与简单说明,起始状态即使用者端发出请 求时,终止状态则相当于绘制器发出响应时: 第 2 页 共 18 页 JSF 自定义组件 不包括事件处理,JSF总共必须经过六个阶段: ¾ 重建视图(Restore View) 对于选择的页面如果是初次浏览则建立新的组件树。如果是会话阶段,会从 客户端或服务器端的数据找寻数据以恢复每个组件的状态并重建组件树,如果不 包括请求参数,则直接跳过接下来的阶段直接绘制响应。 ¾ 套用请求值(Apply Request Values) 每个组件尝试从到来的请求中找寻自己的参数并更新组件值,在这里会触发 ActionEvent,这个事件会被排入队列中,然后在调用应用程序阶段之后才会真 正由事件处理者进行处理。 然而对于设定immeduate为true的命令(Commamnd)组件来说,会立即处理 事件并跳过之后的阶段直接绘制响应,而对于设定immediate为true的输入 (Input)组件,会马上进行转换验证并处理值变事件,之后跳过接下来的阶段, 直接绘制响应。 ¾ 执行验证(Process Validations) 进行转换与验证处理,如果验证错误,则会跳过之后的阶段,直接绘制响应, 结果是重新调用同一页绘制结果。 ¾ 更新模型值(Update Model Values) 更新每一个与组件绑定的backing bean或模型对象。 ¾ 调用应用程序(Invoke Application) 处理动作事件,并进行后端应用程序逻辑。 ¾ 绘制响应(Render Response) 使用绘制器绘制页面。 如果您只是要“使用”JSF,则您最基本的只需要知道“执行验证”、“更 新模型值”、与“调用应用程序”这三个阶段及中间的事件触发,JSF参考实现 将这三个阶段之外的其它阶段的复杂性隐藏起来了,您不需要知道这几个阶段的 处理细节。 然而如果您要自定义组件,则您还必须知道“重建视图”、“套用请求值” 与“绘制响应”这些阶段是如何处理的,这几个阶段相当复杂,所幸的是您可以 使用JSF 所提供的框架来进行组件自定义,JSF提供的框架已经很大程度上降低 了组件制作的复杂性。 当然,即使JSF框架降低了复杂性,但实际上要处理JSF自定义组件还是很复 杂的一件事,在尝试开发自定义组件之前,您可以先搜寻一些网站,像是 Apache MyFaces( http://myfaces.apache.org/),看看是不是已经有相关类似的组件 已经开发完成,省去您重新自定义组件的气力。 1.2 概述自定义组件 所谓的“自定义JSF组件”是一个概略的称呼,事实上,一个JSF组件包括了 三个部份:Tag、Component 与Renderer。 Tag即之前一直在使用的JSF标签,类似于HTML标签,JSF标签主要是方便网 页设计人员进行版面配置与数据呈现的一种方式,实际的处理中,JSF标签的目 的在于设定Component属性、设定验证器、设定数据绑定、设定方法绑定等等。 第 3 页 共 18 页 JSF 自定义组件 Component的目的在于处理请求,当请求来到服务器端应用程序时,每一个 Component都有机会根据自己的client id,从请求中取得属于自己的值,接着 Component可以将这个值作处理,然后设定给绑定的bean。 当请求来到Web应用程序时,HTTP中的字符串内容可以转换为JSF组件所需的 值,这个动作称之为解码(decode),相对的,将JSF组件的值转换为HTTP字符 串数据并送至客户端,这个动作称之为编码(encode),Component可自己处理 编码、解码的任务,也可以将之委托给 Renderer来处理。 当您要自定义Component时,您可以继承UIComonent或其相关的子类,这要 根据您实际要自定义的组件而定,如果您要自定义一个输出组件,可以继承 UIOutput,如果要自定义一个输入组件,则可以继承UIInput,每一个标准的JSF 组件实际上都对应了一个UIComponent的子类,下图为一个大致的类继承结构图: 实际上要自定义一个组件是复杂的一件工作,您首先要学会的是一个完整的 自定义组件流程,实际上要自定义一个组件时,您可以参考一下网络上的一些成 品,例如 Apache MyFaces(http://myfaces.apache.org/),接下来后面的几 个主题所要介绍的,将只是一个自定义组件的简单流程。 Renderer是一个可替换的组件,您的Component可以搭配不同的Renderer, 而不用自行担任绘制响应或解码的动作,这会让您的Component可以重用,当您 需要将响应从HTML转换为其它的媒介时(例如移动电话网络),则只要替换 Renderer就可以了,这是一个好处,或者您可以简单的替换掉一个Renderer,就 可以将原先简单的HTML响应,替换为有JavaScript功能的Renderer。 当您开始接触自定义组件时,您会开始接触到JSF的框架(Framework),也 许有几个类会是您经常接触的: javax.faces.component.UIComponent 自定义Component所要继承的父类,但通常,您是继承其子类,例如UIInput、 UIOutput等等。 javax.faces.webapp.UIComponentTag 自定义JSF标签所要继承的父类,继承它可以帮您省去许多JSF标签处理的细 节。 第 4 页 共 18 页 JSF 自定义组件 javax.faces.context.FacesContext 包括了JSF相关的请求信息,您可以通过它取得请求对象或请求参数,或者 是javax.faces.application.Application对象。 javax.faces.application.Application 包括了一个应用程序所共享的信息,像是locale、验证器、转换器等等,您 可以通过一些 工厂方法 取得相关的信息。 2. 简单实例 在不考虑组件有子组件的情况下,这里以实际的一个例子来说明开发组件的 过程,至于考虑子组件的情况请参考专书介绍。 2.1 编码、解码 Component可以自己负责将对象数据编码为HTML文件或其它的输出文件,也 可以将这个任务委托给 Renderer,这里先介绍的是让Component自己负责编码的 动作。 这里着重的是介绍完成自定义组件所必须的流程,所以我们不设计太复杂的 组件,这里将完成以下的组件,这个组件会有一个输入文字框以及一个提交按钮: 您要继承UIComponent或其子类来自定义Component,由于文字框是一个输入 框,为了方便,您可以继承UIInput类,这可以让您省去一些处理细节的功夫, 在继承UIComponent或其子类后,与编码相关的主要有三个方法: z encodeBegin() z encodeChildren() z encodeEnd() 其中encodeChildren()是在包括子组件时必须定义,Component如果它的 getRendersChildren()方法返回true时会调用encodeChildren()方法,缺省为, getRendersChildren()方法返回false。 由于我们的自定义组件相当简单,所以将编码的动作写在encodeBegin()或 是encodeEnd()都可以,我们这里是定义encodeBegin ()方法: • UITextWithCmd.java package onlyfun.caterpillar; import java.io.IOException; import java.util.Map; import javax.faces.component.UIInput; 第 5 页 共 18 页 JSF 自定义组件 import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; public class UITextWithCmd extends UIInput { private static final String TEXT = ".text"; private static final String CMD = ".cmd"; public UITextWithCmd() { setRendererType(null); } public void encodeBegin(FacesContext context) throws IOException { ResponseWriter writer = context.getResponseWriter(); String clientId = getClientId(context); encodeTextField(writer, clientId); encodeCommand(writer, clientId); } public void decode(FacesContext context) { // ..... } private void encodeTextField(ResponseWriter writer, String clientId) throws IOException { writer.startElement("input", this); writer.writeAttribute("name", clientId + TEXT, null); Object value = getValue(); if(value != null) { writer.writeAttribute("value", value.toString(), null); } String size = (String) getAttributes().get("size"); if(size != null) { writer.writeAttribute("size", size, null); } writer.endElement("input"); } private void encodeCommand(ResponseWriter writer, 第 6 页 共 18 页 JSF 自定义组件 String clientId) throws IOException { writer.startElement("input", this); writer.writeAttribute("type", "submit", null); writer.writeAttribute("name", clientId + CMD, null); writer.writeAttribute("value", "submit", null); writer.endElement("input"); } } 在encodeBegin()方法中,我们取得ResponseWriter对象,这个对象可以协 助您输出HTML标签、属性等,我们使用getClientId()取得组件的id,这个id是 每个组件的唯一识别,缺省为如果您没有指定,则JSF会自动为您产生id值。 接着我们分别对输入文字框及提交按钮作HTML标签输出,在输出时,我们将 name属性设成clientId与一个字符串值的结合(即TEXT或CMD),这是为了方便 在解码时,取得对应name属性的请求值。 在encodeTextField中我们有调用getValue()方法,这个方法是从UIOutput 继承下来的,getValue()方法可以取得Component的设定值,这个值可能是静态 的属性设定值,也可能是JSF Expression的绑定值,缺省会先从组件的属性设定 值开始找寻,如果找不到,再从绑定值(ValueBinding对象)中找寻,组件的属 性值或绑定值的设定,是在定义Tag时要做的事。 编码的部分总结来说,是取得Component的值并作适当的HTML标签输出,再 来我们看看解码的部分,这是定义在decode()方法中,将下面的内容加入至上面 的类定义中: .... public void decode(FacesContext context) { Map reqParaMap = context.getExternalContext(). getRequestParameterMap(); String clientId = getClientId(context); String submittedValue = (String) reqParaMap.get(clientId + TEXT); setSubmittedValue(submittedValue); setValid(true); } .... 我们必须先取得RequestParameterMap,这个Map对象中填入了所有客户端传 来的请求参数, Component在这个方法中有机会查询这些请求参数中,是否有自 己所想要取得的数据,记得我们之前解码时,是将输入框的name属性解码为 client id加上一个字符串值(即TEXT设定的值),所以这时,我们尝试从 RequestParameterMap中取得这个请求值。 取得请求值之后,您可以将数据借由setSumittedValue()设定给绑定的 bean,最后调用setValid()方法,这个方法设定为true时,表示组件正确的获得 自己的值,没有任何的错误发生。 由于我们先不使用Renderer,所以在构造函数中,我们设定RendererType 为null,表示我们不使用Renderer进行解码输出: 第 7 页 共 18 页 JSF 自定义组件 public UITextWithCmd() { setRendererType(null); } 在我们的例子中,我们都是处理字符串对象,所以这里不需要转换器,如果 您需要使用转换器,可以调用setConverter()方法加以设定,在不使用 Renderer 的时候,Component要设定转换器来自行进行字符串与对象的转换。 2.2 组件标签 完成Component的自定义,接下来要设定一个自定义Tag与之对应,自定义Tag 的目的,在于设定 Component属性,取得Componenty类型,取得Renderer类型值 等;属性的设定包括了设定静态值、设定绑定值、设定验证器等等。 要自定义与Component对应的Tag,您可以继承UIComponentTag,例如: • TextWithCmdTag.java package onlyfun.caterpillar; import javax.faces.application.Application; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.el.ValueBinding; import javax.faces.webapp.UIComponentTag; public class TextWithCmdTag extends UIComponentTag { private String size; private String value; public String getComponentType() { return "onlyfun.caterpillar.TextWithCmd"; } public String getRendererType() { return null; } public void setProperties(UIComponent component) { super.setProperties(component); setStringProperty(component, "size", size); setStringProperty(component, "value", value); } private void setStringProperty(UIComponent component, 第 8 页 共 18 页 JSF 自定义组件 String attrName, String attrValue) { if(attrValue == null) return; if(isValueReference(attrValue)) { FacesContext context = FacesContext.getCurrentInstance(); Application application = context.getApplication(); ValueBinding binding = application.createValueBinding(attrValue); component.setValueBinding(attrName, binding); } else { component.getAttributes(). put(attrName, attrValue); } } public void release() { super.release(); size = null; value = null; } public String getSize() { return size; } public void setSize(String size) { this.size = size; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } 首先看到这两个方法: public String getComponentType() { return "onlyfun.caterpillar.TextWithCmd"; 第 9 页 共 18 页 JSF 自定义组件 } public String getRendererType() { return null; } 由于我们的Component目前不使用Renderer,所以getRendererType()返回 null值,而getComponentType()在于让JSF取得这个Tag所对应的Component,所 返回的值在faces-config.xml中要有定义,例如: .... onlyfun.caterpillar.TextWithCmd onlyfun.caterpillar.UITextWithCmd .... 借由faces-config.xml中的定义,JSF可以得知 onlyfun.caterpillar. TextWithCmd的真正类,而这样的定义方式很显然的,您可以随时换掉 所对应的类,也就是说,Tag所对应的Component是可以随时 替换的。 在设定Component属性值时,可以由component.getAttributes()取得Map对 象,并将标签属性值存入Map 中,这个Map对象可以在对应的Component中使用 getAttributes()取得,例如在上一个主题中的UITextWithCmd中可以如下取得存 入Map的size属性: • UITextWithCmd.java package onlyfun.caterpillar; import java.io.IOException; import java.util.Map; import javax.faces.component.UIInput; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; public class UITextWithCmd extends UIInput { .... private void encodeTextField(ResponseWriter writer, String clientId) throws IOException { .... String size = (String) getAttributes().get("size"); 第 10 页 共 18 页 JSF 自定义组件 if(size != null) { writer.writeAttribute("size", size, null); } ..... } .... } 可以使用isValueReference()来测试是否为JSF Expression Language的绑 定语法,如果是的话,则我们必须建立ValueBinding对象,并设定值绑定: .... private void setStringProperty(UIComponent component, String attrName, String attrValue) { if(attrValue == null) return; if(isValueReference(attrValue)) { FacesContext context = FacesContext.getCurrentInstance(); Application application = context.getApplication(); ValueBinding binding = application.createValueBinding(attrValue); component.setValueBinding(attrName, binding); } else { component.getAttributes(). put(attrName, attrValue); } } .... 如果是value属性,记得在上一个主题中我们提过,从UIOutput继承下来的 getValue()方法可以取得 Component的value设定值,这个值可能是静态的属性 设定值,也可能是JSF Expression的绑定值,缺省会先从组件的属性设定值开始 寻找,如果找不到,再从绑定值(ValueBinding对象)中寻找。 最后,我们必须提供自定义Tag的tld档: • textcmd.tld 1.0 第 11 页 共 18 页 JSF 自定义组件 2.0 textcmd http://caterpillar.onlyfun.net/textcmd textcmd onlyfun.caterpillar.TextWithCmdTag empty size value true 2.3 使用自定义组件 在Component与Tag自定义完成后,这里来看看如何使用它们,首先定义 faces-config.xml: • faces-config.xml onlyfun.caterpillar.TextWithCmd onlyfun.caterpillar.UITextWithCmd someBean onlyfun.caterpillar.SomeBean 第 12 页 共 18 页 JSF 自定义组件 session 中定义Component的类型与实际的类对应,在您于自定义Tag中 调用getComponentType()方法所返回的值,就是寻找设定的值 对应,并由此得知真正对应的Component类。 我们所编写的SomeBean测试类如下: • SomeBean package onlyfun.caterpillar; public class SomeBean { private String data; public String getData() { return data; } public void setData(String data) { this.data = data; } } 这里写一个简单的网页来测试一下我们编写的自定义组件: • index.jsp <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="/WEB-INF/textcmd.tld" prefix="oc" %> Input data: 第 13 页 共 18 页 JSF 自定义组件 2.4 自定义 Renderer Component可以将解码、编码的动作交给Renderer,这让您的表现层技术可 以轻易的抽换,我们可以将之前的自定义组件的解码、编码动作移出至Renderer, 不过由于我们之前设计的Component是个很简单的组件,事实上,如果只是要新 增一个Command在输入框旁边,我们并不需要大费周章的自定义一个新的组件, 我们可以直接为输入框更换一个自定义的Renderer。 要自定义一个Renderer,您要继承javax.faces.render.Renderer,我们的 自定义Renderer如下: • TextCmdRenderer.java package onlyfun.caterpillar; import java.io.IOException; import java.util.Map; import javax.faces.component.EditableValueHolder; import javax.faces.component.UIComponent; import javax.faces.component.UIInput; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.render.Renderer; public class TextCmdRenderer extends Renderer { private static final String TEXT = ".text"; private static final String CMD = ".cmd"; public void encodeBegin(FacesContext context, UIComponent component) throws IOException { ResponseWriter writer = context.getResponseWriter(); String clientId = component.getClientId(context); encodeTextField(component, writer, clientId); encodeCommand(component, writer, clientId); } public void decode(FacesContext context, UIComponent component) { Map reqParaMap = context.getExternalContext(). getRequestParameterMap(); String clientId = component.getClientId(context); String submittedValue = (String) reqParaMap.get(clientId + TEXT); 第 14 页 共 18 页 JSF 自定义组件 ((EditableValueHolder) component).setSubmittedValue( submittedValue); ((EditableValueHolder) component).setValid(true); } private void encodeTextField(UIComponent component, ResponseWriter writer, String clientId) throws IOException { writer.startElement("input", component); writer.writeAttribute("name", clientId + TEXT, null); Object value = ((UIInput) component).getValue(); if(value != null) { writer.writeAttribute("value", alue.toString(), null); } String size = (String) component.getAttributes().get("size"); if(size != null) { writer.writeAttribute("size", size, null); } writer.endElement("input"); } private void encodeCommand(UIComponent component, ResponseWriter writer, String clientId) throws IOException { writer.startElement("input", component); writer.writeAttribute("type", "submit", null); writer.writeAttribute("name", clientId + CMD, null); writer.writeAttribute("value", "submit", null); writer.endElement("input"); } } 这个自定义的Renderer其解码、编码过程,与之前直接在Component中进行 解码或编码过程是类似的,所不同的是在解码与编码的方法上,多了UIComponent 参数,代表所代理绘制的Component。 接下来在自定义Tag上,我们的TextWithCmdTag与之前主题所介绍的没什么 差别,只不过在getComponentType()与 getRendererType()方法上要修改一下: • TextWithCmdTag.java package onlyfun.caterpillar; 第 15 页 共 18 页 JSF 自定义组件 import javax.faces.application.Application; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.el.ValueBinding; import javax.faces.webapp.UIComponentTag; public class TextWithCmdTag extends UIComponentTag { private String size; private String value; public String getComponentType() { return "javax.faces.Input"; } public String getRendererType() { return "onlyfun.caterpillar.TextCmd"; } ..... } getComponentType()取得的是"javax.faces.Input",它实际上对应至 UIInput类,而getRendererType()取回的是"onlyfun.caterpillar.TextCmd", 这会在faces-config.xml中定义,以对应至实际的Renderer类: • faces-config.xml .... javax.faces.Input onlyfun.caterpillar.TextCmd onlyfun.caterpillar.TextCmdRenderer .... 为Component定义一个Renderer,必须由component family与renderer type 第 16 页 共 18 页 JSF 自定义组件 共同定义,这并不难理解,因为一个Component可以搭配不同的Renderer,但它 是属于同一个component family,例如UIInput就是属于javax.faces.Input这个 组件家族,而我们为它定义一个新的Renderer。 接下未完成的范例可以取之前主题介绍过的,我们虽然没有自定义组件,但 我们为UIInput置换了一个新的Renderer,这个Renderer会在输入框上加入一个 按钮。 如果您坚持使用之前自定义的UITextWithCmd,则可以如下修改: • UITextWithCmd.java package onlyfun.caterpillar; import javax.faces.component.UIInput; public class UITextWithCmd extends UIInput { public UITextWithCmd() { setRendererType("onlyfun.caterpillar.TextCmd"); } } 我们只是单纯的继承UIInput,然后使用setRendererType()设定"onlyfun. caterpillar.TextCmd",但并没有为组件加入什么行为,看来什么事都没有做, 但事实上这是因为继承了UIInput,它为我们处理了大多数的细节。 接下来同样的,设定自定义Tag: • TextWithCmdTag.java package onlyfun.caterpillar; import javax.faces.application.Application; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.el.ValueBinding; import javax.faces.webapp.UIComponentTag; public class TextWithCmdTag extends UIComponentTag { private String size; private String value; public String getComponentType() { return "onlyfun.caterpillar.TextWithCmd"; } public String getRendererType() { return "onlyfun.caterpillar.TextCmd"; } 第 17 页 共 18 页 JSF 自定义组件 ..... } 要使用自定义的Component,记得要在faces-config.xml中再加入: .... onlyfun.caterpillar.TextWithCmd onlyfun.caterpillar.UITextWithCmd ... 第 18 页 共 18 页

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享文档获得金币 ] 1 人已下载

下载文档

相关文档