世事如棋
Aspire to Professionalism
posts - 4,  comments - 12,  trackbacks - 0
作者:Andrei Cioroianu
转自:Oracle Technology Network http://www.oracle.com/technology/global/cn/pub/articles/andrei_reuse.html

了解如何利用 JSP 标记文件、JSF 和 Oracle ADF Faces 重用 Web 内容和 Java 代码。

本文相关下载:
示例代码
Oracle JDeveloper 10g (10.1.3)

2005 年 10 月发表

代码重用是提高开发人员生产效率和应用程序可维护性的一种非常好的方式。您应当总是寻找设计良好的框架和可自定义的组件,而不是从头重来。应用程序特有的代码也可以在模块甚至相关项目间重用。后一种可重用性可使您快速修改,整体利用新特性,并减少测试和调试的时间。

虽然这些听起来像是针对程序员的不错建议,但 Web 开发人员也应当注意这些事情。许多 Web 开发人员已经在使用诸如 Java Server Faces (JSF)、Oracle ADF Faces 和 Apache MyFaces 之类的框架,这些框架提供了许多内置组件并支持创建其他可重用组件。然而,很多时候是将许多 HTML 和 JSP 标记从一个 Web 页面复制粘贴到其他页面中,这意味着当 Web 内容改变时将不得不修改这些页面中的重复标记。此外,如果没有更新某些页面,那么应用程序的外观将会不一致。如果跨页面重用 UI 组件就不会发生这种情况了,这是因为发生变化时只需在一个地方进行编辑就可以了。

在本文中,我将提供一些在基于 JSF 和 ADF Faces 的 Web 应用程序中重用 UI 组件的最佳实践。您将了解到如何创建定义 Web 页面布局的模板,以及如何重用表单、菜单和按钮栏。您还将了解到如何转换现有的 JSP 页面以使它们更易于维护,以及如何将由 JSF 和 Oracle ADF Faces 提供的组件与 JSTL 和现代 JSP 特性(例如标记文件)一起使用。

Java Web 可重用特性

自从第一个版本起,JSP 就已经提供了一些鼓励可重用的基本机制,例如 JavaBeans 支持、基于 Servlets API RequestDispatcher 的 <%@include%> 指令和 <jsp:include> 标记。JSTL 增加了 <c:import> 标记,它使您能够包含某个资源的内容,该资源可以位于同一个应用程序、服务器中,甚至也可以在远程服务器上。Struts Tiles 围绕着这种内容包含特性构建了一个完整的框架。JSF 也支持这一特性,允许您构建使用 <f:subview> 标记的子表单。JSP 2.0 增加了一个称为“隐式包含”的新特性。这些特性使用 <include-prelude><include-coda> 在 web.xml 文件中声明。正如您所能看到的,虽然页面/片断包含种类各异,但每一种都有其自己的用途和上下文。

对自定义标记的支持从 JSP 1.1 就有了,它为构建标记库提供了一个 API。JSP 1.2 对该 API 进行了增强,但很多人认为它太复杂了。因此,JSP 2.0 定义了一个具有相同功能的全新 API。这个为标记库提供的新 API 称为简单标记 API,旧 API 现在称为标准标记 API。许多 Web 框架(如 Struts、JSF 和 JSTL)仍使用标准标记 API,以便可以与 JSP 1.2 以及 JSP 2.0 一起使用。简单标记 API 是另一种 JSP 2.0 特性 — 标记文件 — 的基础,该特性使您能够使用 JSP 语法构建标记库。除了简单标记和标记文件之外,JSP 2.0 规范还定义了 EL 函数,后者使您能够使用 EL 语法从 JSP 页面中调用静态 Java 方法。

JSF 标准将组件定义为它的可重用单元。这些组件比自定义标记更强大,但也更难设计和实施。因为有几个公司和开放源代码机构正在制作可供使用的 JSF 组件库,所以您可能不需要构建自己的 JSF 组件。本文的示例使用了 Oracle ADF Faces,它是基于 JSF 标准的最先进的框架。

创建页面模板。典型 Web 应用程序的所有页面共享一个公共布局,该布局可以定义在一个地方,如 JSP 标记文件中。该模板可以生成标题和正文标记、应用程序的菜单以及在所有页面中出现的其他部分。此外,它可以包含用于加载资源绑定、设置 JSP 变量等的设置标记。在应用程序的每个 Web 页面中重复该标记是没有意义的。在这一部分中,您将了解如何使用 Oracle JDeveloper 10g (10.1.3)(撰写此文时为早期试用版)基于 JSF 和 Oracle ADF Faces 构建自定义模板。

JDeveloper 提供了一个创建 JSF 页面模板的向导。从 File 菜单中选择 New 项,打开 New Gallery 窗口。然后,转至 Web Tier 中的 JSF 类别,在右侧面板中选择 JSF JSP Template 并单击 OK:

图 1

单击 Next 跳过 Welcome 页面,然后选择您使用的 J2EE 版本,并再次单击 Next:

图 2

为模板提供一个文件名:

图 3

选择组件绑定样式:

图 4

指定是否要使用错误页面:

图 5

选择要使用的标记库:

图 6

提供页面标题和其他页面属性:

图 7

单击 Finish。JDeveloper 将创建该模板并在可视化编辑器中将其打开。您可以使用 Component Palette 将 JSF 和 Oracle ADF Faces 组件添加到该模板中。然后,您可以在 New Gallery 窗口中从 Template 中选择 JSF JSP,基于您刚创建的模板创建 JSF 页面。这是从模板构建页面的一种非常简单的方法。另一种方法是将该共用的 JSF 标记移到一个可重用的标记文件中。以下段落使用了第二种方法。

创建标记文件。从 File 菜单中选择 New 项,打开 New Gallery 窗口。然后,转至 Web Tier 中的 JSP 类别,在右侧面板中选择 JSP Tag File 并单击 OK:

图 8

JDeveloper 将打开一个创建 JSP 标记文件的向导窗口。单击 Next 跳过 Welcome 页面,在 File Name 域中输入 pageTemplate.tag 并单击 Next:

图 9

现在您就可以定义模板标记的属性了。假定您正在构建一个基于 Web 的向导,您希望每个页面都有一个步骤 ID 和一个标题。标记文件需要该信息来为每个页面自定义模板标记。单击 Add 按钮,输入 step 属性名,并将 Required 设为 true。对另一个名称为 title 的属性执行同样的操作:

图 10

单击 Next 和 Finish。JDeveloper 将在 WEB-INF 目录的 tags 子目录下创建 pageTemplate.tag 文件。用 <%@attribute%> 指令定义这两个标记属性:
<%@ attribute name="step" required="true" %>
<%@ attribute name="title" required="true" %>
在创建标记文件之后,JDeveloper 将打开它进行编辑。以下段落介绍了本文通篇用到的示例应用程序的模板代码。

在标记文件中使用 JSF 和 Oracle ADF Faces。无论您是否想在普通页面的标记文件内使用这些框架,您都必须用 <%@taglib%> 指令来对其进行声明。在 Component Palette 中选择 JSP,然后单击 Taglib。您需要在一个对话框中输入要使用的标记库的 URI 和前缀:

图 11

您还可以使用 Component Palette 来将任何标记拖放到 JSP 页面上,如果它的 <%@taglib%> 指令不在页面中,那么 JDeveloper 将自动添加它。pageTemplate.tag 示例使用了四个标记库:JSTL Core、JSF Core、Oracle ADF Faces Core 和 Oracle ADF Faces HTML:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="af" 
uri="http://xmlns.oracle.com/adf/faces" %>
<%@ taglib prefix="afh" 
uri="http://xmlns.oracle.com/adf/faces/html" %>
在同时使用 JSF 和 JSTL 时,或者在标记文件中使用 JSF 时有一件非常重要的事情您必须始终牢记:JSF 框架不支持页面范围,后者是 JSTL 标记的默认 JSP 范围。因此,您应当显式指定与 JSTL、JSF 和 Oracle ADF Faces 的标记结合使用的 JSP 变量的请求范围。此外,标记文件的所有属性都可以通过保存在页面范围内的 JSP 变量来访问。您必须复制请求范围内的属性,以便它们可以和 JSF 和 Oracle ADF Faces 标记一起使用:
<c:set var="pageTitle" scope="request" value="${pageScope.title}"/>
您还可以将属性的值存储在由 JSF 管理的 Bean 内。假定您有一个名称为 WizardBean 的类,该类在 faces-config.xml 中被配置为受管 Bean:
<faces-config>
    ...
<managed-bean>
<managed-bean-name>wizard</managed-bean-name>
<managed-bean-class>webreuse.WizardBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
    ...
</faces-config>
示例 Bean 有一个 currentStep 属性,您可以在该属性中存储 step 属性的值。该操作可以利用 JSTL 的 <c:set> 标记来完成,但您必须确保该 Bean 存在于会话范围中(如 faces-config.xml 文件中所声明的那样)。只有在 JSF EL 表达式中首次引用受管 Bean 实例时,JSF 才会创建该实例。如果在访问受管 Bean 的 JSF 或 Oracle ADF Faces 标记之前执行 JSTL 标记,那么,您应当使用 <jsp:useBean>以确保该 Bean 实例存在于会话范围中。如此,您就可以安全地使用 <c:set> 标记了:
<jsp:useBean class="webreuse.WizardBean" 
id="wizard" scope="session"/>
<c:set target="${wizard}" property="currentStep" 
value="${pageScope.step}"/>
以上代码可以手动输入,或者可以使用 JDeveloper 的 Component Palette。选择 JSP,然后单击 UseBean,添加 <jsp:useBean> 标记。JDeveloper 将打开一个对话框,您必须在其中提供该标记的属性:

图 12

最后,您可以在 pageTemplate.tag 文件中添加模板代码:
<f:view>
<afh:html>
<afh:head title="#{pageTitle}"/>
<afh:body>
<af:panelPage title="#{pageTitle}">
<jsp:doBody/>
</af:panelPage>
</afh:body>
</afh:html>
</f:view>
所有的 JSF 和 Oracle ADF Faces 组件都必须置于 <f:view> 内部。<afh:html><afh:head><afh:body> 组件将生成具有相同名称的 HTML 标记。<af:panelPage> 组件显示页面标题。然后,<jsp:doBody> 将调用您放在使用模板标记的 JSP 页面中的 <tags:pageTemplate></tags:pageTemplate> 之间的 JSP 内容。这种 JSP 页面将类似于:
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>

<tags:pageTemplate step="..." title="...">
... JSP content executed by doBody ...
</tags:pageTemplate>
当执行该页面时,标记文件将生成 <html><head><body> 标记以及页面标题和其他标题标记。然后,模板标记文件将调用包在 <tags:pageTemplate></tags:pageTemplate> 之间的 JSP 内容。在此之后,标记文件可能会生成一个页脚,并用 </body></html> 来完成页面。下一部分将在基于 Web 的向导的页面中使用模板标记。

重用表单、菜单和其他 UI 组件

利用 JSF 和 Oracle ADF Faces 组件构建的 UI 面板可以使用 "subviews" 或 JSP 标记文件在多个页面中重用。前一种解决方案与旧的 JSP 1.2 版本兼容,但标记文件更灵活。这一部分将介绍如何基于 JSF 和 Oracle ADF Faces 将一个 Web 表单分成多个页面和标记文件。当用户第一次访问应用程序时,他将使用包含后退和前进按钮的向导式界面来逐步浏览这些页面。在此以后,他可能想更新最初提供的信息。在这种情况下,用户需要使用基于菜单的界面直接访问应用程序的页面。

可以在上述的两种情况下使用同一种 Web 表单。此外,所有的表单可以组合在一个确认页面中,该页面可以让用户以只读模式查看信息。当用户必须修改其中一个表单时,可以编辑单个文件。每一个修改都将显示在向导风格的界面(用于从用户那获取信息)、确认页面(用于查看信息)和基于菜单的界面(由用户用于更新信息)中。如果不重用表单,您将必须在三个不同的地方进行相同的修改。

开发 Backing Bean。您在前一部分中已经发现,示例应用程序使用了一个名称为 WizardBean 的 Backing Bean。pageTemplate.tag 文件设置了 currentStep 属性,该属性保存用户在浏览器中看到的当前表单的步骤 ID。示例应用程序在确认页面(第四步)之前使用了三个表单,确认页面的 ID 由 MAX_STEP 常量来定义。将该常量公开为名为 maxStep 的 bean 属性,以便可以在 Web 页面中使用 JSF EL 来访问它:
package webreuse;

public class WizardBean implements java.io.Serializable {
public final static int MAX_STEP = 4;
private int currentStep;
private String connName, connType;
private String userName, password, role;
private String driver, hostName, sid;
private int jdbcPort;

public int getMaxStep() {
return MAX_STEP;
    }

public int getCurrentStep() {
return currentStep;
    }

public void setCurrentStep(int currentStep) {
this.currentStep = currentStep;
    }

    ...
}
currentStepmaxStep 之外,WizardBean 类还有几个其他的属性可用于保存用户提供的向导参数:connNameconnTypeuserNamepasswordroledriverhostNamesidjdbcPort。该示例应用程序与用户数据无关,但在实际情况中,向导将用它来配置数据库连接。WizardBean 还实施了几个在用户单击向导的按钮时将执行 JSF 操作。这些方法稍后将在本部分中进行介绍。

要创建您自己的 Bean,您可以在 File 菜单中选择 New 来打开 New Gallery 窗口。然后,转至 General 中的 Simple Files 类别,在右侧面板中选择 Java Class,并单击 OK:

图 13

提供类名和程序包名称。然后单击 OK,创建该类:

图 14

在声明一个字段之后(例如 private int currentStep),右键单击其名称并选择 Generate Accessors。单击 OK,生成 get 和 set 方法:

图 15

创建可重用表单。将向导的主表单编写为可重用标记文件。第一个表单 (form1.tag) 包含一个文本域和一个下拉列表:
<!-- form1.tag -->

<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>

<af:inputText id="connName" required="true" columns="40"
label="Connection Name:" value="#{wizard.connName}"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>

<af:selectOneChoice id="connType" required="true"
label="Connection Type:" value="#{wizard.connType}"
readOnly="#{wizard.currentStep == wizard.maxStep}">
<af:selectItem label="Oracle (JDBC)" value="Oracle_JDBC"/>
</af:selectOneChoice>
第二个表单 (form2.tag) 包含三个文本域:
<!-- form2.tag -->

<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>

<af:inputText id="userName" required="true" columns="40"
label="User Name:" value="#{wizard.userName}"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>

<af:inputText id="password" required="true" columns="40"
label="Password:" value="#{wizard.password}" secret="true"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>

<af:inputText id="role" required="false" columns="40"
label="Role:" value="#{wizard.role}"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>
第三个表单 (form3.tag) 与其他两个表单类似:
<!-- form3.tag -->

<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>

<af:selectOneChoice id="driver" required="true"
label="Driver:" value="#{wizard.driver}"
readOnly="#{wizard.currentStep == wizard.maxStep}">
<af:selectItem label="thin" value="thin"/>
<af:selectItem label="oci8" value="oci8"/>
</af:selectOneChoice>

<af:inputText id="hostName" required="true" columns="40"
label="Host Name:" value="#{wizard.hostName}"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>

<af:inputText id="sid" required="true" columns="40"
label="SID:" value="#{wizard.sid}"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>

<af:inputText id="jdbcPort" required="true" columns="40"
label="JDBC Port:" value="#{wizard.jdbcPort}"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>
正如您可能已经注意到的那样,三个标记文件中没有一个包含了用于排列 Oracle ADF Faces 组件的标记。不含任何布局标记使得您可以独立地使用表单标记,或在确认页面中组合它们。Oracle ADF Faces 提供了一个名称为 <af:panelForm> 的强大组件,它将自动执行布局。除了主要的组件之外,表单通常包含有其他的标记,例如 <h:messages globalOnly="true"/><af:objectLegend name="required"/>。所有这些标记都可以集中在一个名为 formTemplate.tag 的标记文件中:
<!-- formTemplate.tag -->

<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>

<h:panelGrid columns="1" border="0" cellspacing="5">
<h:messages globalOnly="true"/>
<af:objectLegend name="required"/>
<af:panelForm>
<jsp:doBody/>
</af:panelForm>
</h:panelGrid>
使用 pageTemplate.tag 和 formTemplate.tag 的 JSF 页面将类似于:
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>

<tags:pageTemplate step="..." title="...">
	<af:form id="...">
		...
		<tags:formTemplate>
			<tags:form123/>
		</tags:formTemplate>
		...
	</af:form>
</tags:pageTemplate>
在前面的代码段中,<tags:form123/> 代表向导的三个主表单中的任意一个(&lt;tags:form1/><tags:form2/><tags:form3/>)。除了这些表单的组件之外,<af:form> 可能包含其他的 JSF 和 Oracle ADF Faces 组件(例如按钮)。下一段介绍了使用 <tags:pageTemplate><tags:formTemplate><tags:form1><tags:form2><tags:form3> 的应用程序页面。这些具体的例子充分说明了利用可重用标记文件构建 JSF 用户界面的实际好处。

向导风格的界面。基于 Web 的向导的页面将包含标记为 Back、Next 和 Finish 的按钮。可以在一个名为 stepButtons.tag 的标记文件中定义这些按钮:
<!-- stepButtons.tag -->

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>

<af:panelButtonBar>

<af:singleStepButtonBar
selectedStep="#{wizard.currentStep}"
maxStep="#{wizard.maxStep}"
previousAction="#{wizard.previousAction}"
nextAction="#{wizard.nextAction}"/>

<c:if test="${wizard.currentStep == wizard.maxStep}">
<af:commandButton text="Finish"
action="#{wizard.finishAction}"/>
</c:if>

</af:panelButtonBar>
WizardBean 类包含当用户单击按钮时将执行的操作方法:
package webreuse;

public class WizardBean implements java.io.Serializable {
    ...

public String previousAction() {
if (currentStep <= 1)
return null;
else {
currentStep--;
return "step" + currentStep;
        }
    }

public String nextAction() {
if (currentStep >= getMaxStep())
return null;
else {
currentStep++;
return "step" + currentStep;
        }
    }

public String finishAction() {
currentStep = 0;
return "finished";
    }

    ...
}
操作方法返回的结果将在 faces-config.xml 文件的导航规则中使用:
<faces-config>
    ...
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>step1</from-outcome>
<to-view-id>/step1.jsp</to-view-id>
</navigation-case>
</navigation-rule>
    ...
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>step4</from-outcome>
<to-view-id>/confirm.jsp</to-view-id>
</navigation-case>
</navigation-rule>

<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>finished</from-outcome>
<to-view-id>/index.jsp</to-view-id>
</navigation-case>
</navigation-rule>
    ...
</faces-config>
除了所有可见的组件之外,向导页面还将包含一个与 WizardBean 的 currentStep 属性绑定的隐藏字段。您已经看到了 pageTemplate.tag 将在每一次执行页面时设置该属性。然而,用户可能单击浏览器的后退按钮。作为该操作的结果,在浏览器中看到的当前步骤将与 currentStep 属性的值不符,因为浏览器将从其缓存中检索到页面,而不是请求执行 JSF 页面。

如果每一次用户单击按钮时 currentStep 值都与表单数据一起提交,则不会导致任何问题。hiddenData.tag 文件将 currentStep 作为一个隐藏字段包含在内。此外,如果 currentStep 等于 maxStep(这意味着标记在确认页面中使用),那么该标记文件将为所有 Bean 属性生成隐藏字段:
<!-- hiddenData.tag -->

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>

<h:inputHidden id="currentStep" value="#{wizard.currentStep}"/>

<c:if test="${wizard.currentStep == wizard.maxStep}">
<h:inputHidden id="h_connName" value="#{wizard.connName}"/>
<h:inputHidden id="h_connType" value="#{wizard.connType}"/>
<h:inputHidden id="h_userName" value="#{wizard.userName}"/>
<h:inputHidden id="h_password" value="#{wizard.password}"/>
<h:inputHidden id="h_role" value="#{wizard.role}"/>
<h:inputHidden id="h_driver" value="#{wizard.driver}"/>
<h:inputHidden id="h_hostName" value="#{wizard.hostName}"/>
<h:inputHidden id="h_sid" value="#{wizard.sid}"/>
<h:inputHidden id="h_jdbcPort" value="#{wizard.jdbcPort}"/>
</c:if>
所有的向导部分都可以在 JSF 页面中组装,如 step1.jsp:
<!-- step1.jsp -->

<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>

<tags:pageTemplate step="1" 
title="Create Database Connection - Step 1">
<af:form id="form1">
<tags:formTemplate>
<tags:form1/>
</tags:formTemplate>
<tags:stepButtons/>
<tags:hiddenData/>
</af:form>
</tags:pageTemplate>
step2.jsp 和 step3.jsp 页面与 step1.jsp 非常类似。作为练习,您可以尝试为这些页面构建一个模板,从而将这些页面都减少为四行代码。confirm.jsp 页面将一起显示所有三个表单,但组件在只读模式下工作,从而占用的屏幕空间更少并且无需用户交互:
<!-- confirm.jsp -->

<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>

<tags:pageTemplate step="4" title="Confirm Connection Parameters">
<af:form id="form4">
<tags:formTemplate>
<tags:form1/>
<tags:form2/>
<tags:form3/>
</tags:formTemplate>
<tags:stepButtons/>
<tags:hiddenData/>
</af:form>
</tags:pageTemplate>
基于菜单的界面。假定用户逐步浏览向导的页面,提供所有需要的信息。如果用户需要在以后修改某些地方,那么他应当不需要再次浏览所有的向导页面。相反,用户界面将让用户直接转至必须修改信息的表单。menuTabs.tag 文件使用相同名称的 Oracle ADF Faces 组件来构建菜单:
<!-- menuTabs.tag -->

<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>

<af:menuTabs>
<af:commandMenuItem text="Name and Type" action="tab1"
selected="#{wizard.currentStep == 1}"/>
<af:commandMenuItem text="Authentication" action="tab2"
selected="#{wizard.currentStep == 2}"/>
<af:commandMenuItem text="Connection" action="tab3"
selected="#{wizard.currentStep == 3}"/>
</af:menuTabs>
菜单的导航规则在 faces-config.xml 中定义:
<faces-config>
    ...
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>tab1</from-outcome>
<to-view-id>/tab1.jsp</to-view-id>
</navigation-case>
</navigation-rule>
    ...
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>tab3</from-outcome>
<to-view-id>/tab3.jsp</to-view-id>
</navigation-case>
</navigation-rule>
    ...
</faces-config>
当用户单击菜单的标签时,表单数据将被提交给 Web 服务器,在该服务器上 JSF 框架将更新 Backing Bean。如果用户想更新表单而不改变当前的标签,那么需要使用提交按钮。submitButton.tag 文件提供了提交按钮:
<!-- submitButton.tag -->

<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>

<af:commandButton id="command" text="Submit" action="submitAction"/>
tab1.jsp、tab2.jsp 和 tab3.jsp 文件将把菜单附加到向导的表单上。下面是 tab1.jsp:
<!-- tab1.jsp -->

<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>

<tags:pageTemplate step="1" title="Edit Database Connection">
<af:form id="form1">
<tags:menuTabs/>
<tags:formTemplate>
<tags:form1/>
</tags:formTemplate>
<tags:submitButton/>
<tags:hiddenData/>
</af:form>
</tags:pageTemplate>
使显示逻辑可重用

如果您必须使用 JSF 和 Oracle ADF Faces 从头开始构建新的页面,那么一切都没什么问题。但是,对于包含了以 Java 代码形式存在的显示逻辑的旧 JSP 页面,该如何处理呢?维护这些页面困难重重,并且,若不将显示代码从 JSP 页面中分离出来,则无法对其进行重用。此外,Java 代码和 HTML 标记不应混合在同一个页面中,因为这将使 Java 开发人员和 Web 设计人员无法轻松地展开并行工作。

JSF 和 Oracle ADF Faces 组件解决了多数情况下的此种问题,因为 Web 页面将使用这两个框架提供的标记来构建,同时将 Java 代码放到了 Backing Bean 中。JSTL 和其他的标记库也非常有用,但有时您必须只能使用 Java 代码来动态生成内容。

一种好的解决方案是使用 JSP 2.0 提供的 Simple Tags API 来构建标记库。该 API 使您能够开发标记处理器类,但您必须维护一个单独的 XML 文件(称为标记库描述符 (TLD)),该文件定义标记名称、它们的属性等。如果这听起来太复杂,那么您可以简单地将 Java 代码移到使用 JSP 语法的标记文件中,让应用服务器生成标记处理器类和 TLD 文件。让我们看一下名为 oldCode.jsp 的 JSP 页面,它混合了 Java 和 HTML。该页面将读取一个文本文件(其路径将作为一个请求参数提供)并显示文件的内容(包括行号)。当您构建演示应用程序并想显示代码时,这将非常有用。

重要注意事项!请勿在生产环境中使用本部分的示例(oldCode.jsp 和 newCode.jsp),因为它们可能会泄漏应用程序的源代码。

oldCode.jsp 页面使用 java.io API 来读取文本文件。它将在 JSP 页面范围中为每一行文本创建两个名为 lineTextlineNo 的变量。行号将用 JSTL 的 <fmt:formatNumber> 标记来进行格式化,文本将通过 <c:out> 标记进行显示:
<!-- oldCode.jsp -->

<%@ page import="java.io.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<HTML>
<HEAD>
<TITLE>${param.path}</TITLE>
</HEAD>
<BODY>

<c:if test="${empty param.path}">
<P>The <CODE>path</CODE> parameter wasn't specified.
</c:if>

<c:if test="${!empty param.path}">
<P><B><CODE>${param.path}</CODE></B>
<%
String path = application.getRealPath(
request.getParameter("path"));
BufferedReader in = new BufferedReader(new FileReader(path));
try {
int lineNo = 0;
String lineText;
while ((lineText = in.readLine()) != null) {
lineNo++;
pageContext.setAttribute("lineText", lineText);
pageContext.setAttribute("lineNo",
new Integer(lineNo));
%>
<fmt:formatNumber var="fmtLineNo"
value="${lineNo}" minIntegerDigits="3"/>
<PRE>${fmtLineNo}  <c:out value="${lineText}"/></PRE>
<%
        }
} finally {
in.close();
    }
%>
</c:if>

</BODY>
</HTML>
来自前一个页面示例的全部 Java 代码都可以移到一个名为 readTextFile.tag 的标记文件中。只需进行少许修改:您必须使用 <%@tag%> 指令和 jspContext 隐式对象,而不是 <%@page%> 和 pageContext。您还必须使用 JSP 指令来声明属性和变量:
<!-- readTextFile.tag -->

<%@ tag import="java.io.*" %>
<%@ attribute name="path" required="true" %>
<%@ variable name-given="lineText" scope="NESTED" %>
<%@ variable name-given="lineNo" scope="NESTED" %>
<%
String path = application.getRealPath(
(String) jspContext.getAttribute("path"));
BufferedReader in = new BufferedReader(new FileReader(path));
try {
int lineNo = 0;
String lineText;
while ((lineText = in.readLine()) != null) {
lineNo++;
jspContext.setAttribute("lineText", lineText);
jspContext.setAttribute("lineNo",
new Integer(lineNo));
%>
<jsp:doBody/>
<%
        }
} finally {
in.close();
    }
%>
您可以在任何需要逐行处理文本文件的 JSP 页面中使用 readTextFile.tag 文件。每一个页面都可以对文本行执行任何需要的操作,因为该标记文件使用了 <jsp:doBody/>,从而允许 JSP 页面将处理当前行的代码放到 <tags:readTextFile></tags:readTextFile> 之间。newCode.jsp 页面的功能与旧样式的示例相同,但它没有混合 Java 和 HTML:
<!-- newCode.jsp -->

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>

<HTML>
<HEAD>
<TITLE>${param.path}</TITLE>
</HEAD>
<BODY>

<c:if test="${empty param.path}">
<P>The <CODE>path</CODE> parameter wasn't specified.
</c:if>

<c:if test="${!empty param.path}">
<P><B><CODE>${param.path}</CODE></B>
<tags:readTextFile path="${param.path}">
<fmt:formatNumber var="fmtLineNo"
value="${lineNo}" minIntegerDigits="3"/>
<PRE>${fmtLineNo}  <c:out value="${lineText}"/></PRE>
</tags:readTextFile>
</c:if>

</BODY>
</HTML>
正如您所见,修改旧的 JSP 页面以便可以重用代码并不是很难。维护也变得更加容易。

随意重用

重复的代码或内容是最令人头疼的事情。有时它可能是一种容易的解决方案,但修改应用程序变得更加困难,这意味着从长远来看,您将不能适应新的用户需求或快速地修复问题。在理想情况下,应用程序不应包含相同代码的多个版本,Web 内容不应被复制和粘贴。
posted on 2006-05-10 14:53 KingWell 阅读(373) 评论(0)  编辑  收藏 所属分类: Java Server Faces

只有注册用户登录后才能发表评论。


网站导航:
 
欢迎访问我的网站
JSF中国

<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

常用链接

留言簿(1)

随笔档案

文章分类

文章档案

收藏夹

我的资源

搜索

  •  

最新评论

阅读排行榜

评论排行榜