2006 年 11 月发布
最近,我所工作的机构(威斯康星大学伊奥克莱尔分校)将一个用 Cobol 编写且运行在 Unisys 大型机上的老旧系统的一部分移植到了 java/J2EE 环境中。
我们的 IT 小组主要使用开放源代码工具(包括 Eclipse、Tomcat 和 SPRing)来编写应用程序。但是,许可方式上的更改使我们可以考虑 Oracle 工具,因此我们使用 Oracle JDeveloper 10g 和 Oracle 应用程序开发框架 (ADF) 编写了一个应用程序来测试开发模型。本文将对该应用程序进行描述,从而快速而具体地介绍如何使用 Oracle ADF 进行 UI 开发。(有关更进一步的技术信息,请参阅 Oracle 技术网上的 Oracle ADF 教程和 Oracle ADF 开发人员指南、Oracle ACE Steve Muench 在 Oracle 杂志上内容出色的 ADF 专栏以及 Oracle JDeveloper 论坛中的评论。)
在本文中,我不会描述构建应用程序所需的每一个步骤,但希望这里的信息足以使您作出自己的决策。本文假设您十分了解 Java,熟悉 Eclipse 或 JDeveloper,并具有普通编程知识。(我没有在每个步骤后提醒您 Save All,但该步骤不应省略。)
改变给定体系结构和开发模型总是很困难,因此我将首先提供一些背景知识,以说明 JDeveloper 和 ADF 的价值所在,从而证实这种转变的有利性。我还会在一个单独的部分中提供一些“提示与技巧”。
背景
当前应用程序直接使用了 Java (JDK 1.5)、Spring(MVC、Web Flow、JDBC 模板)和 jsp 页面。数据也复制到了一个 Oracle 数据库 10g 第 2 版数据库中。尽管我们了解如何使用 JBoss Hibernate 作为对象关系映射 (ORM) 工具,但我们的数据访问既不复杂也不是动态的,而且 Spring JDBC 模板已经充分满足了我们的需要。
由于我们在生产中使用的是 Oracle 数据库 10g 第 2 版,因此在基于 Windows 的试用版中使用 Oracle 数据库 10g 快捷版 (XE)(Oracle 数据库的免费入门级版本)顺理成章,我们希望能够在其中轻松地访问数据。在决定使用 Oracle 的 TopLink ORM 工具后使用 Oracle 数据库也很有意义,这样我们就可以轻松地将映射移植到其他数据源(假如决定使用测试用例作为生产应用程序的基础)。我们对 XE 所作的唯一更改就是在系统主页中更改系统全局区 (SGA),以减少它使用的内存。
我们选择的 TopLink 比预期的效果还要好,因为它抽象了许多使用 JDBC 甚至接口(如 Spring 的 JDBC 模板)的繁复的具体信息。(Dustin Marx 在 OTN 上的“在 JDBC 编程中添加一些 Spring 代码”一文中提供了一个优秀示例,该示例使用 JDBC 模板,无需配置复杂的 Spring 环境。)JBoss Hibernate 是一个出色的 ORM 工具,但在端到端 ADF 解决方案中集成 TopLink 映射的能力也令人大开眼界并且非常高效。
根据 Oracle 的建议,我们创建了一个 EJB 会话 Bean 来封装对数据库的访问。这只有在应用程序和数据库之间有一个层(以便在其中根据需要使用业务逻辑)的情况下才有意义。迄今为止,我们的应用程序中没有任何需要状态的事物,而使用 session Facade 设计模式时通常会需要状态。
使用 ADF 的一个标准步骤是创建一组能够访问数据信息库的数据控件,我们将遵循该原则。在不需要业务逻辑的情况下(通常是查询,有时是更新和删除),通过 UI 控件直接访问信息十分高效。在较复杂的情况下,可以通过在会话 Bean 方法中包含业务规则实现来应用业务规则。(ADF 开发人员指南第 1.1 章很好地说明了 ADF 和 JSF 如何协同工作以提供高效的开发环境。)
我们还使用标准的 JSF 导航创建了一组 jspx 文件。尽管我们熟悉 Struts,但这里并不使用它。我们将使用 jspx 文件,因为这是 Oracle 建议的做法。
JDeveloper 10.1.3 在整个项目中充当 IDE。这可能很明显,但忘了提醒您它无法识别 JDeveloper 在构建 ADF 应用程序的过程中所扮演的基本角色。JDeveloper 是一个 Java 开发工具,但它还可以为您提供对 TopLink、ADF UI 及数据访问控件的功能、JSF 功能以及 xml 和 JSP 文件的端到端访问,还可以提供测试部署和数据库访问环境。
同时,当您在 JDeveloper 中编辑文件时,系统会自动在窗口中调整更改,您在使用 Eclipse 和其他 IDE 时通常看不到这些更改。这些更改提供了各种视图和编辑方法,因此学习使用 JDeveloper 的丰富功能是有一定难度的。您需要了解在哪里双击、何时使用 StrUCture 和 Data Control 窗口、何时在属性编辑器中设置值,以及(偶然)何时使用 Source 窗口并编写 Java 或 JSP 代码。
项目
我们的项目是一个简单的学生会银行帐户。所需的基本功能是输入和编辑支出与存款。该项目基本上是 ADF 教程中描述的功能子集,因此熟悉 SRDemo 的人可以识别出其中许多元素。SRDemo 是一个完整的示例应用程序,您可以通过 JDeveloper 的 Help 下拉菜单中的“检查更新”机制下载该应用程序。该程序包含完整的源代码,ADF 开发人员指南使用它来说明如何使用 ADF 进行开发。
第 0 阶段:构建应用程序的初始设置
第 1 步:使用 JSF、EJB 和 TopLink 模板创建一个新的 Web 应用程序。(另请参阅 Oracle ADF 教程 1-10。)选择模板并不重要,所有选项都可用,即使它们最初没有被选中。通过该模板,创建标准的 Model 和 ViewController 项目,从而保留应用程序的前端和后端。您应该设置默认程序包名称,因为在创建 Java 类时,该名称可在 JDeveloper 中用作默认名称。 第 2 步:设置数据库。尽管我们可以尝试使用 New Gallery 的 Database Tier 中的 Offline Database Objects,但我们先前已经创建了所需的 SQL,以使用一组表(3 个)创建模式。我们登录到 XE Web 界面,并使用脚本(您可以在 SQL Command 页面上的引用中找到该脚本)创建了用户和表。为这些表指定主键很重要,这样至少可以使 TopLink 具有足够的信息来生成更复杂的映射:
Account 用于保存有关帐户的基本信息。
Details 用于保存所有帐户的交易信息。
Manager 是与帐户关联的个人列表。
为了填充表,我们编写了一个使用 DBUnit 的 Java 应用程序,并使用引用中的数据集创建了一个 CampusAccounts.xml 文件。假如愿意,您可以克隆 XML 元素来扩展数据集。由于这是应用程序中的第一个 Java 类,我们选择在 Model 项目中创建一个 New/General/Java 类,以便创建程序包。然后,将类的主体复制并粘贴到新创建的类中。假如类已经创建,我们将选择 File/Import 选项将 Java Source 复制到 Model 项目中。 public static void main(String[] argv) throws Exception {
Class driverClass = Class.forName("oracle.jdbc.OracleDriver");
Connection jdbcConnection =
DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE",
"CampusAccounts", "xxxxxx");
IDatabaseConnection connection = new DatabaseConnection(jdbcConnection);
IDataSet dataSet = new FlatXmlDataSet(new File("c:\\CampusAccounts.xml"));
try {
DatabaSEOperation.CLEAN_INSERT.execute(connection, dataSet);
}
finally {
connection.close();
}
}
主要片段的主体演示了利用 DBUnit 功能是多么简单。它显示了用于连接到 Oracle Database XE 数据库的 URL,这也是标准 Oracle JDBC 驱动程序的名称。
但是,您需要在项目中添加一个来自 DBUnit 的 jar 文件和一个库,以编译代码。首先,您需要从 DBUnit zip 文件中提取 jar 文件。从 Model 项目的 Properties 中,选择 Libraries/Add Jar,然后将 DBUnit jar 文件添加到项目中。通过从 Model 属性中选择 Libraries/Add Library,您可以添加 Oracle JDBC 库,该库包含 Oracle JDBC 驱动程序。一旦 jar 文件和库可用,就可以使用 JDeveloper 的 Alt-Enter 代码完成选项自动添加所需的导入。(确保导入了 java.sql.Connection。)要运行该 Java 应用程序,请在 applications 窗口中选择该文件和绿色箭头。一旦连接创建(下一步),就简单多了。
第 1 阶段:创建初始模型
第 3 步:在创建模式之后创建 TopLink 对象关系映射。(另请参阅 Oracle ADF 教程 1-8 和 2-2。)在使用 TopLink 之前需要创建一个连接。选择 View/Connection Navigator(假如窗口未打开)。
选择 New Connection on Database,指定一个名称,并确保选择了 Oracle (JDBC) 类型。输入模式名称和密码。假如您使用的是 XE,则将 sid 更改为 XE。测试连接以确保成功。
返回到 Applications 窗口,右键单击 Model,然后从 Tables 中选择 New/Business Tier/TopLink/Java Objects。为映射命名并更改连接(假如需要)。查询第 2 步中的模式,并加入所有表。检查程序包名称。系统在您指定的程序包中创建了一组数据访问对象 (DAO),每个表一个。通过打开 Model 的 TopLink 文件夹中的映射,您可以看到每个模式表的域映射。假如您在 Structure 窗口中选择了一个表,Map 窗口就会转而显示映射的不同视图。尝试这个操作是种不错的做法,因为它可以让您熟悉 JDeveloper 如何在窗口中调整更改。
在 Detail 映射中选择 Queries 选项卡,您会注重到 TopLink 为每个表自动创建了一个 findAll 查询。要添加一个简单查询(将得到表行的较小子集),请在 Named Queries 中选择 Add,并为查询命名。然后,在 Parameters 窗口中选择 Add,设置参数类型(通过指定完全限定的 Java 类名,如 java.lang.Integer),然后单击 Name 列更改参数名。针对您需要指定的每个参数选择 Add。在我们的示例中,已经将 findAccountTransactionsByAccount() 添加到 Detail 映射以根据帐号检索特定帐户的所有行,并将 findOneTransaction() 添加到 Detail 映射以检索特定交易,交易通过帐号(序列号对)区分。findOneTransaction() 是一个 ReadObjectQuery,而 findAccountTransactionsByAccount() 是一个 ReadAllQuery:
点击查看大图假如您选择 Format 选项卡,可以使用 TopLink 编辑器创建查询或输入查询字符串。要使用查询表达式创建器,请选择 EXPression/Edit。在创建器中,选择 Add,并将 Second Argument 更改为刚刚创建的参数。假如有其他参数,则再次选择 Add,创建器将自动创建一个 AND 子句:
系统会创建一个默认的 TopLink 会话,对应于所创建的连接。您可以打开 sessions.xml 文件,并通过在 Structure 窗口中选择 Default 来查看默认值。更改诸如日志记录之类的选项非常简单。
第 4 步:创建 EJB 会话。(另请参阅 Oracle ADF 教程 2-14。)对于 Model,选择 New/Business Tier/EJB/Session Bean。无需更改默认设置,除非不需要远程接口。假如查看创建的 Bean,您将看到系统已经创建了可用于表行的 merge、persist、refresh 和 remove 方法。您还将看到,系统已经为您创建的每个命名查询创建了一个方法。EJB 向导自动生成的 Java 代码会在该方法内创建一个会话事务。当然,您不必编辑该代码,并且现有方法的文本也不会被覆盖(假如在重新生成 Bean 时,它仍然是您的首选项)。
第 5 步:创建 ADF 数据控件。(另请参阅 Oracle ADF 教程 2-16。)ADF 数据控件为前面步骤中创建的会话 Bean 和 DAO 提供了一个接口。在创建用于访问数据库的用户界面控件时,数据控件会提供“粘合剂”。假如您从 View 菜单中打开 Data Control 选项板,应该看到它是空白的。右键单击会话 Bean 并选择最后一项:Create Data Control。Data Control 选项板将填充有以下内容:
为项目创建可用后端的初步工作就完成了。尽管诸如 ADF 控件之类的产物的实际值在构建某些页面时才会显现出来,但所花费的精力之少仍然令人惊异。
第 2 阶段:创建初始视图第 6 步:使用初始的 faces-config.xml 文件。在首次打开应用程序时,打开 Web Content/WEB-INF 文件夹和所创建的 faces-config.xml 文件。假如由于某种原因尚未创建配置文件,则右键单击 ViewController 项目并选择 New/Web Tier/JSF/JSF Page Flow & Configuration。faces-config.xml 的默认名称是正确的。假如您选择 Overview 选项卡,将看到配置文件的空白视图:
点击查看大图
在 JSF 应用程序中,该视图是进入中心配置文件的重要窗口。除了一组描述用户如何在 Faces 应用程序中从一个页面转换到另一个页面的 XML 元素以外,还有用于 Bean 的元素,这些 Bean 包括在用户与界面控件(辅助 Bean)交互时调用其方法的 Bean,以及用于存储长久信息(如用户状态)的 Bean。
第 7 步:创建初始导航图和对应页面。(另请参阅 Oracle ADF 教程 3-2。)现在,需要一组初始页面(主要基于 SRDemo 中使用的页面模板),以及描述用户如何从一个页面转移到另一个页面的初始导航规则。选择针对 faces-confix.xml 文件的 Diagram 选项卡,确保 Configuration 选项板处于开启状态,然后将 JSF 页面拖到配置图上。此时,您已经在配置文件(而不是页面)中创建了一个导航元素。
双击无标题页面图标,以启动一个将实际创建文件的向导。将名称更改为 index;将文件后缀更改为 jspx(Oracle 的普遍建议);确保系统没有自动公开该页面的 UI 组件;然后移动 ADF Faces 组件、JSF 内核、ADF Faces 和 JSF Html 库。选择您喜欢的 HTML 版本 — 我们在 4.0.1 Transitional 版上运行良好。
index.jspx 将重定向到应用程序的实际第一页,这是创建 Web 应用程序的标准做法。这可能会创造一个糟糕的先例,但可以在 Source 窗口中查找 index.jspx,并用以下 scriptlet 替换 f:view 标记。注重,您要创建一个目录结构来治理页面 — 治理涵盖所有应用程序的所有页面,而名为 campusaccounts 的子目录将用于保存该项目的所有页面。将 scriptlet 插入 index.jspx 文件之后,您就可以安全地保存并关闭该文件了。<jsp:scriptlet>
response.sendRedirect("faces/administration/welcome.jspx");
</jsp:scriptlet>
从 Component 选项板中,将另一个 JSF 页拖到 faces-config.xml 图上。双击图标打开向导,以设置其 jsp 页面。将名称更改为 welcome,将文件类型更改为 jspx — 并确保设置目录,以创建您希望创建的任何子目录结构。例如,在本案例中,我们将目录设置为 ../public_html/administration,以便对应于 sendRedirect。移动选项应该与创建 index.jspx 时所使用的移动选项相同,HTML 版本也应该相同。
将内容添加到 JSP 页面实际上就是在 Diagram、Property Editor 和 Structure 窗口之间切换。有时,您甚至可以通过 Source 窗口编辑页面。在 Structure 窗口中,打开 faces 视图元素并删除默认的 f:view 标记,以便能够使用包含面板页的以下文本替换它。面板页是一个复杂的 UI 控件,它包含页眉、页脚、用于选择帮助的全局控件以及版权部分等内容。在使用该页时,您将发现使用标准界面组件创建复杂页面是多么轻松。粘贴文本的最简单方法就是切换到 Source 窗口,并确保游标跟随文件开头的三个 jsp 标记。<f:view>
<f:loadBundle basename="edu.uwec.financial.resources.UIResources"
var="res"/>
<af:document title="#{res['uacct.campusAccounts.title']}"
initialFocusId="start">
<af:form>
<af:panelPage>
<!-- Page Content End -->
<f:facet name="branding">
<af:panelHorizontal>
<af:objectImage source="/images/banner.jpg"/>
</af:panelHorizontal>
</f:facet>
<f:facet name="menu1">
<af:menuTabs var="menuTab" value="http://www.QQread.com/oracle/2006/12/#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{menuTab.label}"
action="#{menuTab.getOutcome}"
rendered="#{menuTab.shown and menuTab.type=='default'}"
disabled="#{menuTab.readOnly}"/>
</f:facet>
</af:menuTabs>
</f:facet>
<f:facet name="menu2">
<af:menuBar/>
</f:facet>
<f:facet name="menuGlobal">
<af:menuButtons>
<af:commandMenuItem text="#{res['uacct.menu.logout']}"
action="GlobalLogout" immediate="true"
icon="images/blafIcons/logout.gif"/>
<af:commandMenuItem text="#{res['uacct.menu.help']}"
action="GlobalHelp" immediate="true"
icon="../images/blafIcons/help.gif"/>
</af:menuButtons>
</f:facet>
<f:facet name="messages">
<af:messages/>
</f:facet>
<f:facet name="appCopyright">
<!--Copyright msg-->
<h:panelGroup>
<af:objectSpacer width="10" height="10" id="objectSpacer4"/>
<af:panelHorizontal halign="center">
<af:outputText value="#{res['uacct.copyright']}"/>
</af:panelHorizontal>
</h:panelGroup>
</f:facet>
<f:facet name="appAbout">
<!-- The About this Application Link-->
<af:panelHorizontal halign="center">
<af:commandLink text="#{res['uacct.about']}" action="GlobalAbout"
immediate="true"/>
</af:panelHorizontal>
</f:facet>
<f:facet name="infoUser">
<!-- Show the Logged in user -->
<h:outputFormat value="#{res['uacct.connectedUser']}"
rendered="#{userInfo.authenticated}" escape="false">
<f:param value="#{userInfo.userName}"/>
</h:outputFormat>
</f:facet>
</af:panelPage>
</af:form>
</af:document>
</f:view>
您将注重到 Structure 窗口中报告了错误,这些错误将在下一步中解决。
第 8 步:不择手段地占为己有。(另请参阅 Oracle ADF 教程 4-2。)我们建议从 SRDemo 中借用几个文件,以使您快速了解如何创建应用程序。由于使用资源文件很简单,我们决定始终使用它来标记所有 UI 组件,甚至是按钮上很明显的标记(如 Submit)。
我们预计大部分内联网应用程序都没有国际化需求,但这是一个设计决策,它创造了一个较好的先例。我们还建议借用几个类(如 ADFUtils 和 JSFUtils),因为它们非常有用。假如您必须通过 Java 代码访问 JSF 和 ADF 框架中存储的变量,则有一点麻烦。一旦您了解 Oracle 使用的绑定和命名标准,就会觉得这些实用程序类十分简单,示例如下。导入图像文件夹也很有意义,因为它包含了浏览器外观 (BLAF) 文件夹和界面中使用的几个图标。
我们导入文件的方法是选择 ViewController 项目,选择 File/Import/Java Source,然后浏览到 SRDemo 项目中的类。SRDemo 中的完整路径与 mywork 位于同一级别 — 参见 samples/SRDemo/UserInterface/src/oracle/srdemo/view。将 ADFUtils 和 JSFUtils 添加到 util,然后将它们复制到程序包结构中(我们在 src 文件夹中使用的是 edu/uwec/financial/util);将 ResourceAdapter 和 UIResources 添加到 resources(我们使用的是 edu/uwec/financial/resources);然后通过菜单(我们使用的是 edu/uwec/financial/menu)添加 MenuItem、MenuModelAdapter 和 MenuTreeModelAdapter。注重,当您导入 Java 文件后,可能需要修改程序包名称以匹配目录结构。
您可以导入一些标准图像,方法是选择 File/Import/Web Source,浏览到 UserInterface/public_html,然后选择图像 — 确保将 images 文件夹复制到 public_html 文件夹中。这个步骤有一点麻烦,因为在浏览时很轻易过于深入:
此时,jspx 文件中的错误应该已经消失,但可能需要修正面板页上的元素,例如,要包含在 branding 控件 (facet) 中的所有图像。假如您要将某个图像包含在页面顶部,应将其作为 WebContent 导入 ViewController 项目的 images 文件夹。在 Structure 窗口的面板页中,选择 branding/panelHorizontal,然后在 Properties 窗口中重设其对象图像。您还应该确保页面上的图标(如 logout 和 help)具有正确的 Source 属性。在 Property 窗口的 Source 属性中选择每个图标和 ... 按钮,并在 Source 窗口中查找图像。您需要在 welcome.jspx 中编辑诸如 loadBundle 标记之类的项,以便与程序包结构相匹配。
示例属性文件项与 welcome.jspx 中引用的属性不对应。以下是我们添加的初始名/值对集。uacct.about=About MyBlugold
uacct.copyright=\u00a9 2006 University of Wisconsin - Eau Claire
uacct.menu.help=Help
uacct.menu.logout=Logout
uacct.menu.campusAccounts=Campus Accounts
uacct.campusAccounts.title=Campus Accounts
uacct.connectedUser=Logged in as <b>{0}</b>
ADFUtils 类没有编译,因为它需要一个库。在 ViewController 项目上打开 Properties,然后添加 ADF Model Runtime 库。
假如您选择 index.jspx 并运行应用程序(选择绿色箭头),应该看到与以下类似的内容 — 当然,还包含标帜。
点击查看大图实际上,欢迎页面将为我们提供更加“全局”的按钮(如 Help 和 Logout),因为您可以从任何页面选择它们。
第 9 步:创建菜单。这一部分将介绍从 SRDemo 中借用以创建菜单的代码,假如您希望该简介简短些,可以跳过该部分。
治理顶层菜单所需的标记和 Java 代码已添加至项目,但需要在 faces-config.xml 文件中添加相关项以将其连接。一般而言,JDeveloper 会提供所需项,您也可以打开 faces-config.xml 文件并从 Overview 选项卡添加 Bean 定义。另一种方法是通过 Source 窗口将以下示例复制到文件,从而使用托管 Bean 元素作为项模型以获得类似的 Bean(如 Bean 注释标记中所述)。<!-- Global menu tab for logout (we also added a tab for help -->
<managed-bean>
<managed-bean-name>menuItem_GlobalLogout</managed-bean-name>
<managed-bean-class>edu.uwec.financial.menu.MenuItem</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>#{resources['uacct.menu.logout']}</value>
</managed-property>
<managed-property>
<property-name>icon</property-name>
<value>/images/logout.gif</value>
</managed-property>
<managed-property>
<property-name>type</property-name>
<value>global</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/administration/Logout.jsp</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>GlobalLogout</value>
</managed-property>
</managed-bean>
<!-- Campus Accounts menu tab (we also added tabs for Purchasing and Accounting) -->
<managed-bean>
<managed-bean-name>menuItem_CampusAccounts</managed-bean-name>
<managed-bean-class>edu.uwec.financial.menu.MenuItem</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>#{resources['uacct.menu.campusAccounts']}</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/administration/campusaccounts/CampusAccounts.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>GlobalCampusAccounts</value>
</managed-property>
</managed-bean>
<!-- create the main menu menuModel bean, only one needed -->
<managed-bean>
<managed-bean-name>menuModel</managed-bean-name>
<managed-bean-class>edu.uwec.financial.menu.MenuModelAdapter</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>viewIdProperty</property-name>
<value>viewId</value>
</managed-property>
<managed-property>
<property-name>instance</property-name>
<value>#{menuTreeModel.model}</value>
</managed-property>
</managed-bean>
<!-- create the main menu menuTreeModel bean, only one needed -->
<managed-bean>
<managed-bean-name>menuTreeModel</managed-bean-name>
<managed-bean-class>edu.uwec.financial.menu.MenuTreeModelAdapter</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>childProperty</property-name>
<value>children</value>
</managed-property>
<managed-property>
<property-name>listInstance</property-name>
<list-entries>
<value-class>edu.uwec.financial.menu.MenuItem</value-class>
<value>#{menuItem_GlobalLogout}</value>
<value>#{menuItem_GlobalHelp}</value>
<value>#{menuItem_CampusAccounts}</value>
<value>#{menuItem_Purchasing}</value>
<value>#{menuItem_Accounting}</value>
</list-entries>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>resources</managed-bean-name>
<managed-bean-class>edu.uwec.financial.resources.ResourceAdapter</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
由于这些 Bean 将引用资源文件,因此 faces-config.xml 文件还需要一个为其定义的项。在应用程序标记中,除了添加 default-render-kit 以外,还需要添加以下 xml 元素:<message-bundle>edu.uwec.financial.resources.UIResources</message-bundle>
再次运行 index.jspx 文件将显示一个与以下类似的页面(假如您没有添加用于购买、帮助等的托管 Bean,那么该页面将只显示 Campus Accounts 选项卡):
点击查看大图第 10 步:创建第一个输入页面。第一个“有用的”页面将是一个答应用户输入帐号的页面,这是治理帐户的第一步。打开 faces-config.xml 图并将一个新页面拖至其上,然后双击图标对该页面进行编辑。
在本例中,我们希望为校园帐户 (campusaccounts) 创建另一个组织级别,因此在 /administration/campusaccounts 中创建了 choose.jspx(在 public html 之后添加 administration\campusaccounts)。我们决定让 JDeveloper 在第二步中创建托管 Bean。choose.jspx 的辅助 Bean 将自动命名为 Choose.java,并包含每个用户界面组件的域。这意味着,您在每次添加组件时(即便是如 spacer 般简单的组件),它都会通过自己的 getter 和 setter 显示为一个域。对于编写 Java 代码以更改 spacer 中的属性大小(如高度)来说,这十分有意义,然而还是会因此创建出复杂的辅助 Bean。在本例中,从自动创建的辅助 Bean 开始要更为简单些(假如我们希望了解它执行的操作)。
使用 Structure 窗口,将 替换为 welcome.jspx 中的 view 标记。实质上,您已将欢迎页面用作模板。在 choose.jspx 的 Structure 窗口中,您可以删除 view 标记,然后将 view 标记从 welcome.jspx 复制并粘贴到 choose.jspx 中。假如您选择在 Source 窗口中进行更改,请注重保留指示 jspx 具有辅助 Bean 的 html 注释 — 该 Oracle 惯例如下所示:<!--oracle-jdev-comment:auto-binding-backing-bean-name:backing_administration_campusaccounts_choose-->
向在 GlobalCampusAccounts 上指定导航规则的 faces 配置文件添加导航规则,界面将导航至应用程序的第一个页面。这是一个全局规则,因为您可以通过在任何应用程序的任意位置选择 CampusAccounts 的选项卡来更改为 CampusAccounts。在 faces-config.xml 中输入的标记是<navigation-rule>
<navigation-case>
<from-outcome>GlobalCampusAccounts
<to-view-id>/administration/campusaccounts/choose.jspx
</navigation-case>
</navigation-rule>
您可能需要编辑 commandMenuItems 中的 logout 和 home 图标属性,并更改它们的图标值以使用 blaf 文件夹中的 gif 文件。
您需要将 PanelHorizontal 插入面板页以保存输入文本组件。尽管也可以使用组件选项板 (Component palette),但通常我们认为利用 Structure 页来执行该操作更为简单(右键单击 PanelPage 元素并选择 ADF Faces Core 组件即可)。注重,ADF 表单包含有 PanelPage,因此您已经具有了自己的 html 表单元素。向水平面板添加一个 InputText 组件。在 Properties 窗口中,更改标签以使用存储在资源文件中的属性。一种简单的方法是先编辑资源文件并添加属性,然后在 Structure 窗口中双击 inputText 元素,选择 Bind 在 JSF 对象中查找 res,并选择您刚才输入的属性值。一旦您习惯了访问资源所需的 EL 格式,就会发现在 Property Editor 中键入属性值也很简单。在水平面板上的输入文本组件后面添加一个 CommandButton,并更改其名称以使用资源文件中定义的另一个属性 — 再次建议您使用 Structure 窗口。
双击按钮转至辅助 Bean。您需要从 InputText 组件获取文本,并将其置于用户状态,以便当用户访问 CampusAccounts 应用程序的选项时它始终可用。public String commandButton1_action() {
Object text = getInputText1().getValue();
UserSystemState.storeCurrentAcctNum(new Integer(acctNum));
return "list";
}
注重,此处无需任何验证,而您在使用 Faces 组件的选项时会进行不必要的验证。假如您在 InputText 元素上设置必需属性,则会强迫用户输入值。假如您在 InputText 标记内插入一个 ValidateRegExp 验证程序并添加模式 [0-9]+,则会要求用户至少为帐号输入一位数字。
在本例中,我们添加了来自 SRDemo 的 UserSystemState,以将其用作模型并创建用于保存帐号的静态函数。我们还必须添加 userSystemState(u 为小写) 作为 faces-config.xml 文件中的托管 Bean 的名称。您可以通过 managed-bean-class 域来推断我们使用的程序包名。JDeveloper 治理 faces-config.xml 文件的明显特征之一是,假如在编译代码或指定程序包名时出现问题,您就会在其标记中看到警告。JDeveloper 会主动分析 xml 配置文件。<managed-bean>
<managed-bean-name>userSystemState</managed-bean-name>
<managed-bean-class>edu.uwec.financial.view.backing.UserSystemState</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
以下是 UserSystemState 的起始代码,用于创建 Command 按钮中使用的静态函数;它将替代所有来自 SRDemo 的域和方法。记住检查程序包名:private HashMap _settings = new HashMap();
private static final String CURRENT_ACCT_NUM = "CURRENT_ACCT_NUM";
public Integer getCurrentAcctNum() {
return (Integer)_settings.get(CURRENT_ACCT_NUM);
}
public void setCurrentAcctNum(Integer acctNum) {
_settings.put(CURRENT_ACCT_NUM, acctNum);
System.out.println("setCurrentAcctNum:"+acctNum); // some old fashioned testing
}
public static void storeCurrentAcctNum(Integer acctNum) {
JSFUtils.setManagedBeanValue("userSystemState.currentAcctNum", acctNum);
}
public static void retrieveCurrentAcctNum() {
JSFUtils.getManagedBeanValue("userSystemState.currentAcctNum");
}
该代码比较奇异。userSystemState.currentAcctNum 似乎是 UserSystemState 中某个域的引用,但这个名称没有域。JSFUtils.getManagedBeanValue 函数调用的参数被解析为 getter;因此,userSystemState.currentAcctNum 将导致对 getCurrentAcctNum() 进行调用。
假如运行 index.jspx 并选择 CampusAccounts 选项卡,您应该看到应用程序的初始页面将如下所示:
点击查看大图第 11 步:创建第一个 ADF 数据控件。(另请参阅 ADF 教程 5-2。)这一步布满了乐趣,并且 ADF 的价值将在这一步真正体现出来。创建了答应用户输入帐号的页面后,您现在希望获取所有与该帐号关联的交易 — 文件上的所有存款和支出。
在 faces 导航图中创建一个名为 /administration/campusaccounts/list.jspx 的页面,并在编辑 list.jspx 时自动公开 UI 组件。复制来自 welcome.jspx 的 view 标记,如第 9 步中执行的操作。
您需要连接列表页以显示 Detail 表(或 Detail DAO,具体取决于您如何考虑)中的相应行。请牢记,ADF 数据控件是构建 ADF 应用程序的要害,因此请打开 Data Control 选项板。
您可以将 findAccountTransactionsByAccount() 函数拖放到列表页 — 在这种情况下,您能够创建诸如 Command 按钮或参数表单之类的组件。
而将 Detail 拖放到 panelPage 将创建一组可以从 Detail 表显示的行 — 这些行具有一个通用帐号。考虑到在这种情形下一个较长的组件列表比较有用,因此选择 Tables/ADF Read-only Table...Action Binding Editor 将显示,因此您需要从选择页面获取为帐号存储的值,并将其作为要用于 findAccountTransactionsByAccount 的值进行绑定:
现在,显示所有域并启用选择和排序功能。您还可以通过在 Structure 窗口中双击每一列,来编辑列属性以有选择地启用/禁用排序功能并更改列标题。在本例中,我们向资源文件添加了列标题,然后将 HeaderText 绑定到引用,如 #{res['uacct.campusAccounts.account']}。(您还可以通过在 Structure 窗口中双击组件,来重置表的选择控件中的文本。)我们发现 Bind 按钮比较有用。
假如运行该应用程序并输入一个有效帐号,您希望看到的内容将如下所示:
点击查看大图假如输入 1000 作为帐号,您会发现自己仍位于选择页面。您需要指定一个从选择页面到列表页面的转换。在 faces-config.xml 文件中,打开 Design 窗口并确保 Component 选项板可见。选择 JSF Navigation Case,然后单击选择页面和列表页面(请按此顺序操作)。编辑转换标签并将 success 更改为 list,从而与 Choose.java 中 Command 按钮操作返回的值相对应。现在,数据将显示在列表页面中。
注重显示这个表(包含 Detail 数据库表中的项)是多么轻松。界面组件是一个 ADF JSF 组件,因此可以说您免费获得了包括各种功能在内的复杂显示对象,如分页(总共 33 行结果,一次打印 10 行,假如结果全部匹配,可以一次打印全部 33 行)、分层(每行具有一种不同的颜色)、排序以及通过单选按钮选择单个行的能力(例如,能够编辑其中的内容)。这个页面的外观使用默认的 Oracle 外观,但是,更改用于指定组件标签的 CSS 文件可以获得不同的颜色、字体和图标。
此外,您还会注重到利用 ADF 提供的复杂数据控件可以十分轻松地实现数据访问。在将 ADF Read-Only 表拖放到页面之后,系统会推断返回的辅助对象类型 (Detail.java),并自动将其用于创建相应列。系统提供了行集行为(一次 10 行),正如基于派生自原始数据库表的元数据的类型解析一样。
第 12 步:创建编辑页面。(另请参阅 Oracle ADF 教程 10-2。)在列表页面上,用于选择的 Table 控件 (facet) 包含一个 af:tableSelectOne 标记,该标记内部有一个 Command 按钮。假如双击 Command 按钮,将打开辅助 Bean,您可以在其中调用辅助函数来获取数据以唯一标识选定行,以便将其存储以用于下一页面的检索。public String commandButton1_action() {
setCurrentTransactionIdFromRow();
return "edit";
}
List.java 中的辅助函数如下所示: private void setCurrentTransactionIdFromRow() {
FacesContext ctx = FacesContext.getCurrentInstance();
JUCtrlValueBindingRef tableRowRef =
(JUCtrlValueBindingRef) this.getTable1().getRowData();
Integer transId = (Integer)tableRowRef.getRow().getAttribute("cdPostSequence");
UserSystemState.storeCurrentTransId(transId);
}
需要将 storeCurrentTransId() 添加至 UserSystemState,以便将第 9 步中的 storeCurrentAcctNum() 用作模型。 到目前为止,已经介绍了选择要编辑的行所需进行的更改。现在,需要创建一个要在其中进行编辑的新 jspx 页面。在 faces-config.xml 文件中,添加一个 jsp 页面,然后对其进行编辑以命名为 edit.jspx。在 Component 选项板中,选择 JSF Navigation Case,并在 Design 窗口中将转换标签更改为编辑,以匹配 Command 按钮中指定的返回值。
转到 edit.jspx 页面,从 Data Control 选项板中选择 findOneTransaction/Detail,并使用 Forms/ADF Form 将其拖放至面板页。在其中包含一个 Submit 按钮,并更改域顺序(假如需要)。将参数设置为 JSF 对象中的值,以使用 UserSystemState 中的 acctNum 和 transId。完成操作绑定之后,您可以将不希望用户编辑的域的 readOnly 属性设置为 true。在 Structure 窗口中双击每一个 inputText 域,然后从资源文件设置它们的标签。首先在资源文件中编辑值要更为简单。答应用户从编辑页面退出可能是最好的做法,因此您可以用 PanelGroup 围绕 Submit 按钮,然后在 Submit 之前或之后添加另一个 CommandButton。您需要选择这些按钮并添加返回值。对于 Cancel 按钮,无需调用与该按钮对应的辅助函数,就可以编辑 action 属性并指定导航转换值。Cancel 按钮的一些原始内容(如“cancel”)将生效。在 faces-config.xml Design 窗口中添加一个用于提交返回值的 jsf 导航示例,以便从编辑页面返回列表页面。另一种比较好的方法是,首先在 faces-config.xml 文件中设置导航值,在这种情况下,选择这些按钮的 action 属性值时,该值将显示为选项 — JDeveloper 使用配置文件比较有利。
您仍然需要编写代码来保持在编辑页面上所作的更改。但在本示例中,您将再次使用 ADF 开发范例。换言之,与其在辅助 Bean 中编写代码,不如依靠 JDeveloper/ADF 向导来编写保持更改所需的代码。您还将使用在最初创建 ORM 并生成会话 Bean 时自动创建的 mergeEntity() 函数。 如 ADF 教程第 10 章所建议,在 edit.jspx 页面上打开 Design 窗口,然后打开 Data Control 选项板。将 mergeEntity() 方法拖放到 Submit 按钮上,并选择 Bind Existing CommandButton。下一个显示的页面是 Action Binding Editor。
双击向导下一步中的值框(或假如 ... 按钮显示选择该按钮),打开 ADF Bindings 中的绑定。请牢记该绑定引用 ADF 中提供的所有绑定。打开 findOneTransactionIter 节点并选择 currentRow 中的 dataprovider。构建的表达式(指定当前记录)是 ${bindings.findOneTransactionIter.currentRow.dataProvider}。注重,Submit 按钮的 ActionListener 属性已重置为 #{bindings.mergeEntity.execute}。可将 action 属性设置为“list”,以便完成对数据库的更改后,我们的界面将返回到列表页面。将其值可进行更改的所有文本域的 immediate 属性设置为 true。编辑页面应如下所示:
点击查看大图此外,ADF 可以非常轻松地添加编辑事务的功能。虽然此处未提及,但您还可以向会话 Bean 添加另一个函数,以便在调用 mergeEntity 之前将业务规则应用到编辑域 (Apply Business Rules to Edited Field)。
适可而止
还有许多演示无法全部囊括在本次练习中,但您仍可以通过教程或开发人员指南来完成:
您没有为应用程序添加任何安全措施,以确保只有签名信息库中的用户可以添加记录(或编辑记录)。简单的安全性将答应您打印用户名而不是空值。
您没有处理错误或非凡条件。
您没有使用 ADF 的验证功能。例如,确保输入文本框在用户输入新交易时不为空就比较有意义。
添加一个选择以答应用户从交易列表删除存款或支出,这样比较有趣。
但是您必须适可而止!
感谢并总结
我们衷心感谢 JDeveloper 团队就本文中有关 JDeveloper 功能的语句精确性进行了检查。Oracle 文档具体列出了许多此处未提及的 JDeveloper 特性。
正如我们在简介中所述,在您已经熟悉了某种特定开发方法后很难再转而使用另一种技术。本文中,我们使用了 Spring JDBC 模板进行数据建模、利用 Spring MVC 和 Spring Web Flow 进行导航,以及将 Eclipse 用作开发环境。回想起来,开始我们是希望了解如何在新环境中做我们已经知晓的事情,而选件(如辅助 Bean)则助了我们一臂之力。最初我们没有打算尝试 TopLink,甚至寻求另一种方法来使用 ADF 控件。值得兴奋的是,我们阅读了 Oracle 参考,这些参考使我们对于尝试 ADF 提供的其他功能布满了信心。
对于应用程序开发框架而言,ADF 无疑是一个卓越的工具、一个不错的助手。我们力图通过 ADF 和 JDeveloper 探索出若干其他方法,包括
使我们现有的基于 Spring 的后端服务能够用作可从 ADF Faces 接口调用的 Web 服务。Oracle ACE Lucas Jellema 撰写的网志给予了我们尝试这种想法的灵感。
使用 Oracle XML Publisher 作为构建报表页面的另一种方法
将 BPEL 插件用于 JDeveloper,以根据业务流程中人为干预的需要进行建模。据说,BPEL 之于流程治理正如 SQL 之于数据治理。
Oracle 提供的开发工具系列可以很好地满足我们的需求。
提示与技巧
一不做,二不休。文中数次提及的其当之无愧的一个事实是:JDeveloper 和 ADF 是功能强大的端到端解决方案。它们具有挑战开发环境(如 .net)的实力。例如,我们最初计划不使用 TopLink 将示例应用程序放在一起,并认为在应用程序中编写基于 Spring 的数据访问很轻松。阅读 ADF 教程激发了我们对 TopLink 的好奇,因此我们决定尝试使用它,并在开始时只是认为这是无害的。在为交易添加第一个列表表单花费了大约 90 秒后,我们的态度发生了根本性转变。最初,mergeEntity 函数似乎只是由 TopLink 创建的另一个产物。在添加了编辑页面之后,我们了解了其价值,以及可对 Faces 组件进行无缝访问的 ADF 控件的价值。JDeveloper 提供了集成的开发环境。ADF 不仅是一组 UI 组件,还是一个完善的前端、后端框架,可提供能够显著提高工作效率的工具。
JDeveloper 与 Eclipse 或 Websphere Application Developer 在创建程序包、复制和移动文件方面存在着区别。对于图像和其他非 Java 文件,我们发现复制和移动文件最简单的方法是在 Windows Explorer 中进行操作,然后在 Applications 窗口中进行刷新。使用 File 中的 Import 选项时,请确保编辑 CopyTo 行以将完整的文件夹(程序包)名包括在内。重命名文件和移动文件还会对配置文件和属性值造成重大破坏。我们一般使用 Property Editor 中的 ... 选项来设置图像和其他文件的属性值。编辑配置文件需要敏锐的眼光和一定的耐心。我们可能犯的最严重错误就是在 faces 配置文件中将 campusAccounts 更改为 campusaccounts。页面之间不会出现转换,从而浪费了大量时间。诚然,使用 JDeveloper 时保持程序包一致的确令人烦躁。
下载 SRDemo 非常简单,假如您刚好安装了 JDeveloper。在 Help 菜单中,选择 Check for Updates,然后选择 Official Oracle Extensions。选取 ADF SRDemo,并按向导完成相应操作。要运行它,请转至 SRDemo 应用程序并选择 index.jspx 和绿色箭头。一旦下载了演示程序,您就可以访问其全部文件,包括几乎所有应用程序都需要的帮助类(如 JSFUtils 和 UserSystemState)。您需要为 SRDemo 提供数据库模式。
您不必离开 JDeveloper 就可查看数据库表的值。从 Connections 选项卡中,选择您的 Connection 和模式,然后打开一个表。表值将显示在 Data 选项卡(可能位于窗口底部)中。
Tom Moore 是威斯康星大学伊奥克莱尔分校的高级顾问,专攻知识与技术服务。Tom 持有威斯康星大学密尔沃基分校的计算机科学硕士学位,热衷于 Java 和开放源代码产品(如 Struts 和 Spring)。在他将注重力转向 Oracle 开发工具和框架之前,已是威斯康星大学伊奥克莱尔分校计算机科学系的老成员。