在创建交互式网络应用方面,Java Server Faces(以下建成为JSF)相对于诸如Java Server Pages或Apache Struts等同类技术而言拥有很多优势。JSF十分清晰地分离了应用逻辑层与用户界面表示层,提高了网络应用软件的维护能力,JSF同时还提供了一个架构用于开发和重用网络用户界面组件。
很多网络应用程序的开发者正在转移到JSF上来,但他们同时也发现预定义的JSF用户界面组件受到DHTML功能的限制。一些高级应用,例如监控或商务进程监测,需要和JSF架构兼容的高级视觉组件。
在JSF架构的标准之上开发自定义的用于网络图形用户界面的组件是一件轻松的事情,这些组件可以被应用程序员重用。而且,网络组件的开发者现在可以提供更加复杂的组件,同时可以保证开发者可以轻松的享受这些组件带来的优势和便利。这些JSF用户界面组件必须简洁地整合与部署到JSF运行时架构,而且在应用软件设计过程中,还要较好地整合到提供JSF支持的集成开发环境(IDE)中
除了JSF自带的基本的用户界面架构之外,在第一次开发自定义组件的时候,开发者还会遇到一些缺陷和障碍。在这篇文章当中,我们将要介绍如何构建图形化的JSF组件,这种组件使用纯HTML是非常难以实现的。图形化JSF组件的特色之处在于,不仅需要生成DHTML,而且还需要一些额外的图形生成与客户端交互的支持。我们将使用一个图表组件的例子来演示,这个图表组件是用来提供图表和各种客户端的浏览与交互功能。最后,将展示如何将图表组件整合到支持JSF的集成开发环境中。在理解了这个图表组件的设计过程之后,开发者将对如何实现图形化的JSF组件有更深入的理解,希望能够对他们开发自定义的JSF图形组件有所帮助。
什么是Java Server Faces?
JSF是一个标准的服务器端架构,用于简化网络应用软件表示层的构建。开发人员可以组装这些可复用的用户界面组件来创建网页,将这些组件绑定到应用程序的数据源,并利用服务器端的事件处理器来处理客户端事件。依据JSF规范,组件开发者所制作的组件可以简洁地整合到JSF运行时架构,而且在应用软件设计过程中,还可以整合到与JSF兼容的集成开发环境中。JSR 127规范定义了这样的JSF架构,还同时提供了比如输入栏和按钮等基本用户界面组件的可供参考的实现。JSF组件中的绝大部分都符合HTML 2.0标准中的HTML组件和标签的规范。这些相对简单的组件对于很多网络应用程序来讲已经足够了。
然而,很多应用程序,比如监控或监测系统,需要更复杂的数据表示与交互,例如图表、图示和映射等。因为在HTML中直接生成复杂图形的能力有限,所以设计这些高级组件也并不直观。一种解决方案就是让服务器端的组件将图片传送到客户端,然而,这会带来它自身的问题因为最基本的HTML的图片交互功能是很有限的。所以,最后必须使用JavaScript来实现用户的数据浏览与交互功能。
创建简单的JSF组件
本文将这部分将描述一个非常简单的JSF组件的开发步骤,这个组件的功能是将CSS导入到一个HTML的网页上。这个简单组件的描述和代码将作为基础知识,为下一部分继续讲解高级JSF图表组件打下基础。
图1 展示了如何使用组件及其结果
图1
使用这一组件的好处在于,只要通过JSF动作去改变组件的设定值就能够改变整个页面的外观。
一个JSF组件由一些Java类和配置文件组成,为了创建一个自定义的JSF组件,开发人员需要:
1. 编写一个扩展JSF基础组件类的Java类
2. 为默认的渲染工具编写一个渲染器
3. 编写一个Java类来描述标签,这个标签将用于JSP页面
4. 编写一个标签库定义文件
5. 编写一个JSF配置文件
步骤1:开发组件的Java类
组件类将负责管理代表组件状态的属性,因此,我们必须根据组件的行为(如输入组件或输出组件),为组件选择适当的基类。
在列表A中描述的组件扩展了javax.faces.component.UIOutput,以显示指向某个样式表文件的URL,或内联式样式表的内容。
列表A
import javax.faces.component.*;
public class CSSComponent extends UIOutput {
private Boolean link;
public String getFamily() {
return "faces.CSSFamily";
}
public boolean isLink() {
if (link != null)
return link.booleanValue();
ValueBinding vb = getValueBinding("link");
if (vb != null) {
Boolean bvb = (Boolean) vb.getValue(FacesContext.
getCurrentInstance());
if (bvb != null)
return bvb.booleanValue();
}
return false;
}
public void setLink(boolean link) {
this.link = new Boolean(link);
}
public Object saveState(FacesContext context) {
return new Object[] { super.saveState(context), link };
}
public void restoreState(FacesContext context,
Object stateObj) {
Object[] state = (Object[]) stateObj;
super.restoreState(context, state[0]);
link = (Boolean) state[1];
}
}
代码中“关联”的属性规定了值的类型:要么是一个URL,要么是内联样式。该组件还必须能够在向服务器发送请求期间,使用经过JSF架构处理过的对象,来存储并恢复自己的状态。JSF架构自动调用saveState和restoreState方法,我们可以在组件中实现这两种方法来达到这一目标。
步骤2:编写渲染器
渲染程序有两个作用。首先,渲染程序负责发送适当的HTML程序段,该程序段能在客户端中渲染组件。通常情况下,这个HTML程序段由一些适于渲染整个网络浏览器的HTML标签组成,这个渲染阶段还能发送增强客户端交互性的JavaScript代码。
渲染程序的第二个作用是对来自客户端的数据进行解码,从而对服务器端的组件状态进行更新(比如用户在文本字段输入的文本)。标准渲染程序软件包具有强制 性,但也可以提供其他渲染程序软件包,用于提供其他的客户端表示方法或SVG之类的语言。
通过检验组件的链接属性,在列表2中实现的渲染程序将选择在HTML页面中发送的CSS样式类别。
列表B
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;
public class CSSRenderer extends Renderer {
public void encodeEnd(FacesContext context,
UIComponent component)
throws IOException {
super.encodeEnd(context, component);
if (component instanceof CSSComponent) {
CSSComponent cssComponent =
(CSSComponent) component;
String css = (String)cssComponent.getValue();
boolean isLink = cssComponent.isLink();
if (css != null)
if (isLink)
context.getResponseWriter().write("<link type='text/
css' rel='stylesheet' href='" + css + "'/>");
else
context.getResponseWriter().write("<style>\n" + css
+ "\n<style/>\n");
}
}
}
步骤3:编写标签类
同样,JSF架构提供了用于扩展的基类,来编写与组件相关的标签。该标签类将负责:
定义将在faces-config.xml文件中应用的组件类型和渲染类型,我们将在下一部分具体介绍这个XML文件。
创建JSF组件(由JSF架构来处理)并传递JSF标签中所包含的属性来初始化组件。
在列表C中的标签提供了setter和getter来管理链接和值的属性。
列表C
import javax.faces.Webapp.UIComponentTag;
public class CSSTag
extends UIComponentTag {
private String value;
private String link;
public String getComponentType() {
return "faces.CSSComponent";
}
public String getRendererType() {
return "HTML.LinkOrInlineRenderer";
}
protected void setProperties(UIComponent component)
{
super.setProperties(component);
Application app = getFacesContext().getApplication();
if (value != null)
if (isValueReference(value))
component.setValueBinding("value",
app.createValueBinding(value));
else
component.getAttributes().put("value", value);
if (link != null)
if (isValueReference(link))
component.setValueBinding("link",
app.createValueBinding(link));
else
component.getAttributes().put("link",
new Boolean(link));
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
组件一旦创建,便会调用setPropertie方法,对标签属性进行初始化。每个标签属性要么是文字值,要么是bean属性的一个绑定。
步骤4:编写一个标签库定义(TLD)
TLD是一个XML文件,它通过将标签名与相应的Java类相关联来描述标签。TLD还描述了标签所允许的属性。
在列表D中的这个TLD定义了一个名为“css”的标签,该标签被绑定到CSSTag类。它还声明了链接和值标签的属性。
列表D
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/Web-jsptaglibrary_1_
2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>custom</short-name>
<uri>http://www.ilog.com/jviews/tlds/css.tld</uri>
<description>This tag library contains a tag for a sample custom JSF Component.</description>
<tag>
<name>css</name>
<tag-class>path.to.CSSTag</tag-class>
<description>A component that displays the style inline or a link a to a css fi le</description>
<attribute>
<name>id</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
<description>The id of this component.</description>
</attribute>
<attribute>
<name>binding</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
<description>The value binding expression linking this component to a
property in a backing bean. If this attribute is set, the tag does not
create the component itself but retrieves it from the bean property.
This attribute must be a value binding.</description>
</attribute>
<attribute>
<name>value</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
<description>The inline css text or the url to the css fi le to link.</description>
</attribute>
<attribute>
<name>link</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
<description>Whether the value is a link or the inline style.</description>
</attribute>
</tag>
</taglib>
步骤5:JSF配置文件
为了将一个JSF组件集成到架构中,必须提供一个名为faces-config.xml的配置文件。这个文件将用于JSP自定义标 签处理器的组件类型和渲染器类型与对应的Java类关联起来。它还能描述与每个组件一同使用的渲染器。
列表E定义了faces.CSSFamily组件家族。
列表E
<!DOCTYPE faces-confi g PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Confi g 1.0//EN"
"http://java.sun.com/dtd/Web-facesconfi g_1_0.dtd">
<faces-confi g>
<component>
<component-type>faces.CSSComponent</component-type>
<component-class>path.to.CSSComponent</component-class>
<component-extension>
<component-family>faces.CSSFamily</component-family>
<renderer-type>HTML.LinkOrInlineRenderer</renderer-type>
</component-extension>
</component>
<render-kit>
<renderer>
<component-family>faces.CSSFamily</component-family>
<renderer-type> HTML.LinkOrInlineRenderer </renderer-type>
<renderer-class>path.to.CSSRenderer</renderer-class>
</renderer>
</render-kit>
</faces-confi g>
在这个例子中,该家族由faces.CSSComponent类型的单一组件组成,该类型是与CSSComponent类绑定在一起的。最后, 由CSSRenderer类实现的HTML.LinkOrInlineRenderer类型的渲染器与faces.CSSFamily家族相关联。
开发者还可以通过提供额外的信息将组件整合到支持JSF的集成开发环境(IDE)中,以Sun Creator IDE为例,必须提供一个名为sun-faces-config.xml的XML配置文件,这个配置文件描述了应当显示给IDE的组件属性和一些组件的设计信息。关于Sun Studio Creator的更详细的使用说明和构建自定义JSF组件的信息,我强烈推荐SUN的开发者网站。