一、上一篇文章内容回顾:
在上一篇文章中,我们打算用一个称为一号组件的自定义UI组件实例来说明JSF自定义UI组件的编程步骤,这个一号组件的功能是在屏幕上以指定的重复次数输出指定的字符串,在JSP文件中,我们以类似<x:repeatText text="Hello world!" number="3">这样标签来引用一号组件。为了编写这个一号组件,我们概括了四个步骤,这篇文章中,我们将逐个说明这四个步骤。
二、步骤一:编写组件类
Java的基础是类,组件也不例外。自定义UI组件首先表现为一个类,这个类被称为该组件的组件类。组件类是用来定义组件的属性以及行为特征的。我们以一号组件为例来说明组件类的作用。
一号组件的功能是按指定的重复次数显示指定的字符串,因此,很显然,我们需要给一号组件定义两个属性,分别用来保存指定的字符串和指定的次数。此外,一号组件要在页面上输出字符串,必须有一个相应的方法。
将这两个属性和这个方法组织成一个Java类,就是我们所需的一号组件的组件类。
我们将一号组件类命名为StrRepeat,取字符串(Str)和重复(Repeat)之意。可以从零开始编写这样一个组件类,但更聪明的方法是继承具有类似功能的其他类,因为这将减少很多重复的编码。在JSF中,框架已经提供了很多各种各样的组件类,我们可以从中选择一个合适的组件类来继承。选择哪个类,取决于你的自定义UI组件类要实现什么功能。例如,如果自定义UI组件是用来输出信息的,可以继承UIOutput,如果是用来接收用户输入的,可以继承UIInput类等等,对于一号组件,最合适的继承类是UIComponentBase,因此,我们让一号组件类StrRepeat继承UIComponentBase类,如下列代码所示:
public class StrRepeat extends UIComponentBase{
...
}
按照功能要求,给StrRepeat类添加一个String类型的属性text及其getter和setter方法,一个int类型的属性number及其getter和setter方法,如程序清单 1所示:
程序清单 1 给一号组件类添加text和number属性
private String text;
public String getText() { return text; }
public void setText(String text) { this.text = text; }
private int number;
public int getNumber() { return number; }
public void setNumber(int number) { this.number = number; }
有了属性后,还需要为StrRepeat添加输出字符串方法。这个方法在父类UIComponentBase中即有,方法名称为encodeEnd(),我们需要覆盖它以适合一号组件的功能要求,如程序清单 2所示:
程序清单 2 一号组件类encodeEnd()方法的代码
@Override
public void encodeEnd(javax.faces.context.FacesContext context)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
for(int i = 0; i < this.getNumber(); i++) {
writer.writeText(this.getText(), "text");
}
}
方法名encodedEnd()中,encode的意思是编码,与之相对应的是decode即解码。JSP中,习惯将UI组件翻译为html的过程称为encode,而将html翻译为UI组件的过程称为decode。encode用于将UI组件的内容编码为html,而decode则反过来,用于从html中解码出UI组件的内。以一个用户名输入网页为例,这个网页包含两个组件,“输出文本”组件和“文本字段”组件。当显示网页时,“输出文本”组件value属性变成html中的字符串“请输入用户名”的过程是encode,而提交页面时,用户在html的文本输入框中输入的字符串变成“文本字段”组件的value属性值“张三”的过程则是decode。图 4示出了encode和decode两种过程。图中小椭圆代表JSF的view root,两个大椭圆分别代表两个UI组件,它们一起组成JSF的组件树。组件树是网页的JSF运行时表达形式。
图 4 encode和decode过程
理解了encode的含义后,方法名称encodeEnd()有点令人费解。为什么不直接叫encode()呢?这是因为,JSF定义的encode方法其实是一套,共三种,分别是encodeBegin()、encodeChildren()和encodeEnd()。复杂的包含有子组件的组件可能用到全部三种方法,而类似一号组件这样简单的组件,只需用到其中的一种即encodeEnd()即可。图 5示出了encode方法几种可能的调用组合。
图 5 不同复杂程度的UI组件对encode方法的调用组合
现在我们来分析encodeEnd()方法的代码。ResponseWriter是用来向Http响应输出内容的一个对象,调用其writeText()方法可以输出字符串。ResponseWriter对象是从encodeEnd()方法的入口参数context中获得的,这个context即所谓的JSF上下文,其中包含了JSF框架在处理Http请求时涉及的很多信息。
代码中的this代表一号组件类的一个运行时实例,getNumber()方法获得这个实例中number属性的值,getText()方法获得text属性的值,有了这两个属性值后,循环语句负责重复输出text值number次。这正是我们期望一号组件所做的事情。
请注意,writeText()方法有两个入口参数,其中第二个参数是一个字符串,对一号组件而言无关紧要,暂且放过不提。
关于一号组件类,我们需要关注的就这么多了。概括起来,在NetBeans中,我们可以这样编写自定义UI组件类StrRepeat:
1. 创建一个“Web应用程序”项目,将项目命名为CustomComponent1,选择GlashFish为应用服务器,Java EE 5为Java EE版本,选择“Visual Web JavaServer Faces”为框架;
2. 创建一个Java类,命名为StrRepeat,放在customcomponent1包中,修改其声明,使继承UIComponentBase类;
3. 定义text和number属性及其getter和setter方法;
4. 覆盖父类的encodeEnd()方法。
StrRepeat类完整的(省略了package和import语句)代码如程序清单 3所示。
程序清单 3 一号组件类StrRepeat的完整代码
public class StrRepeat extends UIComponentBase{
@Override
public String getFamily() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void encodeEnd(javax.faces.context.FacesContext context)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
for(int i = 0; i < this.getNumber(); i++) {
writer.writeText(this.getText(), "text");
}
}
private String text;
public String getText() { return text; }
public void setText(String text) { this.text = text; }
private int number;
public int getNumber() { return number; }
public void setNumber(int number) { this.number = number; }
}
除了第一个方法getFamily()以外,代码的其他部分我们都已经分析过,不再赘述。getFamily()方法是一个必须实现的抽象方法,其代码可通过在NetBeans中选择“实现所有抽象方法”而自动生成。自动生成的代码什么也不做,只是简单地抛出一个“尚未支持”的异常。鉴于一号组件很简单,并不使用这个方法,因此,我们保留NetBeans自动生成的代码不变。
图 6示出了NetBeans中StrRepeat组件类的代码。
图 6 NetBeans中组件类StrRepeat的代码
【待续。。。。。。】