6. 日程安排服务 JEE 的 Flex 客户端
现在我们介绍两个基于 JEE 预约 Web 服务的 Flex 客户端。所使用的 IDE 是 Flex Builder 3。该产品的演示版可从 [https://www.adobe.com/cfusion/tdrc/index.cfm?loc=fr_fr&product=flex] 网址下载。 Flex Builder 3是基于Eclipse的开发工具。此外,为了运行Flex客户端,我们使用了Wamp工具中的Apache Web服务器。任何Apache服务器均可满足需求。显示Flex客户端的浏览器必须安装至少Flash Player 9版本的插件。
Flex 应用程序的特点是在浏览器的 Flash Player 插件中运行。在这方面,它们类似于 Ajax 应用程序——后者将脚本嵌入发送到浏览器的页面中,随后在浏览器内执行。 Flex 应用程序并非通常意义上的 Web 应用程序:它是一个由 Web 服务器提供服务的客户端应用程序。从这一点来看,它类似于作为这些服务客户端的桌面应用程序。但有一点不同:它最初是从 Web 服务器下载到安装了能够运行它的 Flash Player 插件的浏览器中的。
与桌面应用程序一样,Flex 应用程序主要由两个部分组成:
- 呈现部分:即在浏览器中显示的视图。这些视图具备桌面应用程序窗口的丰富功能。视图通过一种名为MXML的标记语言进行描述。
- 代码部分:主要处理用户在视图上操作所触发的事件。该代码既可以使用 MXML 编写,也可以使用一种名为 ActionScript 的面向对象语言编写。需要区分两种类型的事件:
- 需要与Web服务器进行交互的事件:例如使用Web应用程序提供的数据填充列表、将表单数据发送至服务器等。Flex提供了一系列方法,可在开发者无需干预的情况下与服务器进行通信。这些方法默认是异步的:在向服务器发送请求期间,用户仍可继续与视图进行交互。
- 不与服务器交换数据而直接修改显示视图的事件,例如从树中拖拽一个元素并将其放入列表中。此类事件完全在浏览器本地处理。
Flex 应用程序通常按以下方式运行:
![]() |
- 在 [1] 中,请求一个 HTML 页面
- 在 [2] 中,该页面被发送出去。它随附了一个二进制文件 SWF(ShockWave Flash),其中包含完整的 Flex 应用程序:所有视图及其事件处理代码。 该文件将由浏览器的 FlashPlayer 插件执行。
![]() |
- Flex客户端通常在浏览器本地运行,除非需要外部数据。此时,它会向[3]服务器请求数据,并以[4]格式接收数据,格式包括XML或二进制格式。 被调用的Web服务器端应用程序可以使用任何编程语言编写,关键在于响应的格式。
我们描述了 Flex 应用程序的运行架构,以便读者清楚地理解它与不使用 Ajax 的传统 Web 应用程序(如前文所述的 Asp.Net 应用程序)之间的区别。在后者中,浏览器是被动的:它只是显示由 Web 服务器构建并发送给它的 HTML 页面。
接下来,我们将提供两个 Flex 客户端的示例,仅为展示 Web 服务客户端的多样性。由于笔者本人也是 Flex 初学者,某些要点可能无法像应有的那样详细阐述。
6.1. 第一个 Flex 客户端
现在,我们将编写第一个 Flex 客户端来显示客户列表。构建的客户端/服务器架构如下:
![]() |
在此架构中,包含两台Web服务器:
- 运行远程Web服务的Glassfish服务器
- 运行远程Web服务Flex客户端的Apache服务器
我们将使用 Flex Builder 3 构建 Flex 客户端:
![]() |
- 在 Flex Builder 3 中,创建一个新项目 [1]
- 为其命名,并指定生成文件夹
![]() |
- 在 [4] 中,为主要应用程序(即将被执行的应用程序)命名
- 在 [5] 中,生成后的项目
- 在 [6] 中,应用程序的主文件 MXML
- 文件 MXML 包含一个视图及其事件处理代码。 [Source] [7] 选项卡可访问 MXML 文件。其中包含描述视图的 <mx> 标签以及 ActionScript 代码。
- 可通过 [Design] 和 [8] 选项卡以图形化方式构建视图。此时,描述该视图的 MXML 标签将自动生成在 [Source] 选项卡中。 反之亦然:直接在 [Source] 选项卡中添加的 MXML 标签,会在 [Design] 选项卡中以图形形式体现。
与之前的 C# 客户端和 Asp.Net 一样,我们将为远程 Web 服务 S [A] 生成本地 C 代理 [B]:
![]() |
要生成 C 代理,Web 服务 JEE 必须处于活动状态。
![]() |
- 在 [1] 中,选择“数据 / 导入 Web 服务”选项
- 在 [2] 中,选择 C 代理类和接口的生成文件夹。
![]() |
- 在 [3] 中,输入远程 Web 服务 S 的 WSDL 文件的 Uri(参见第 4.10.2 节),然后进入下一步
- 在 [4] 和 [5] 中,将 [3] 中指定的 WSDL 文件所描述的 Web 服务
- 在 [6] 中:即将为代理 C 生成的方法列表。请注意,这些并非服务 S 的实际方法。它们的签名不正确。 在此,无论Web服务实际方法的参数数量如何,所展示的每个方法都仅有一个参数。该唯一参数是一个类实例,其字段中封装了远程方法所期望的参数。
- 在 [7] 中:C 语言代理的类和接口将被生成在此包中
- 在 [8] 中:将作为远程 Web 服务代理的本地类名称
- 在 [9] 中:完成向导。
- 在 [10] 中:生成的 C 语言代理类和接口的列表。
![]() |
- en [11]:实现 C 代理方法的类 [WsDaoJpaService]。
生成的类 [WsDaoJpaService] 实现了以下接口 [IWsDaoJpaService]:
/**
* Service.as
* This file was auto-generated from WSDL by the Apache Axis2 generator modified by Adobe
* Any change made to this file will be overwritten when the code is re-generated.
*/
package generated.webservices{
import mx.rpc.AsyncToken;
import flash.utils.ByteArray;
import mx.rpc.soap.types.*;
public interface IWsDaoJpaService
{
//用于 getAllClients 操作的存根函数
/**
* Call the operation on the server passing in the arguments defined in the WSDL file
* @param getAllClients
* @return An AsyncToken
*/
function getAllClients(getAllClients:GetAllClients):AsyncToken;
....
function getAllClients_send():AsyncToken;
...
function get getAllClients_lastResult():GetAllClientsResponse;
...
function set getAllClients_lastResult(lastResult:GetAllClientsResponse):void;
...
function addgetAllClientsEventListener(listener:Function):void;
...
function get getAllClients_request_var():GetAllClients_request;
...
function set getAllClients_request_var(request:GetAllClients_request):void;
...
}
}
- 第 11 行:由类 [WsDaoJpaService] 实现的接口 [IWsDaoJpaService]
- 第 19-31 行:为远程 Web 服务的 getAllClients() 方法生成的各种方法。 唯一与 Web 服务实际暴露的方法相近的是第 19 行。它名称正确但签名不正确:远程 Web 服务的 getAllClients() 方法没有参数。
生成的 C 语言代理中 getAllClients 方法的唯一参数类型为 GetAllClients,具体如下:
/**
* GetAllClients.as
* This file was auto-generated from WSDL by the Apache Axis2 generator modified by Adobe
* Any change made to this file will be overwritten when the code is re-generated.
*/
package generated.webservices
{
import mx.utils.ObjectProxy;
import flash.utils.ByteArray;
import mx.rpc.soap.types.*;
/**
* Wrapper class for a operation required type
*/
public class GetAllClients
{
/**
* Constructor, initializes the type class
*/
public function GetAllClients() {}
}
}
这是一个空类。这可能与目标方法 getAllClients 不接受参数这一事实相符。
现在,让我们来看看为实体 Medecin、Client、Rv 和 Creneau 生成的类。 以类 Client 为例:
package generated.webservices
{
import mx.utils.ObjectProxy;
import flash.utils.ByteArray;
import mx.rpc.soap.types.*;
public class Client extends generated.webservices.Personne
{
public function Client() {}
}
}
类 Client 也是空的。它(第 7 行)继承自以下类 Personne:
package generated.webservices
{
import mx.utils.ObjectProxy;
import flash.utils.ByteArray;
import mx.rpc.soap.types.*;
public class Personne
{
public function Personne() {}
public var id:Number;
public var nom:String;
public var prenom:String;
public var titre:String;
public var version:Number;
}
}
- 第 11-15 行:这里包含在 Web 服务 JEE 中定义的类 Personne 的属性。
至此,代理 C 的主要组件已准备就绪,我们可以开始使用它了。
客户端的主文件 [rdvmedecins01.xml] 如下:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init();">
<mx:Script>
<![CDATA[
import generated.webservices.Client;
...
// 数据
private var ws:WsDaoJpaService;
[Bindable]
private var clients:ArrayCollection;
private function init():void{
...
}
private function loadClients():void{
...
}
...
private function displayClient(client:Client):String{
...
}
]]>
</mx:Script>
<mx:Label text="Liste des clients" fontSize="14"/>
<mx:List dataProvider="{clients}" labelFunction="displayClient"></mx:List>
<mx:Button label="Afficher les clients" click="loadClients()"/>
<mx:Text id="txtMsgErreur" width="454" height="75"/>
</mx:Application>
在此代码中,需区分以下内容:
- 应用程序的定义(第 2 行)
- 应用程序视图的描述(第27-30行)
- 位于 <mx:Script> 标签内的 ActionScript 语言事件处理程序(第 3-26 行)。
首先,让我们来分析应用程序本身的定义及其视图描述:
- 第 2 行:定义
- 视图容器中组件的布局模式。layout="vertical" 属性表示组件将上下排列。
- 视图实例化后需执行的方法,即 c.a.d。所有组件均已实例化之时。属性 creationComplete="init();" 表示应执行第13行中的方法init。creationComplete是类Application可触发的事件之一。
- 第27-30行定义了视图的组件
- 第27行:定义一段文本
- 第28行:定义一个列表,用于放置客户列表。标签 dataProvider="{clients}" 指定了填充该列表的数据源。在此,该列表将通过第11行定义的对象 clients 进行填充。 为了能够写入 dataProvider="{clients}",字段 clients 必须具有属性 [Bindable](第 10 行)。 该属性允许在 <mx:Script> 标签外部引用变量 ActionScript。 字段 clients 的类型为 ArrayCollection,这是一种 ActionScript 类型,用于存储对象列表,此处为 Client 类型的对象列表。
- 第 29 行:一个按钮。其 click 事件已得到处理。 click="loadClients()" 属性表示,当点击该按钮时,应执行第 17 行中的 loadClients 方法。正是该按钮将触发向 Web 服务请求客户列表的操作。
- 第 30 行:一个文本框,用于显示服务器针对前一次请求可能返回的错误消息。
第27-30行将在[Design]选项卡中生成以下视图:
![]() |
- [1]:由第27行的组件Label生成
- [2]:由第 28 行中的组件 List 生成
- [3]:由第 29 行中的组件 Button 生成
- [4]:由第 30 行中的组件 Text 生成
- [5]:一个执行示例
现在让我们查看页面中的代码 ActionScript。该代码负责处理视图事件。
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init();">
<mx:Script>
<![CDATA[
import generated.webservices.Client;
...
// 数据
private var ws:WsDaoJpaService;
[Bindable]
private var clients:ArrayCollection;
private function init():void{
// 实例化 Web 服务代理
ws=new WsDaoJpaService();
// 配置事件处理程序
ws.addgetAllClientsEventListener(loadClientsCompleted);
ws.addEventListener(FaultEvent.FAULT,loadClientsFault);
}
private function loadClients():void{
// 请求客户端列表
ws.getAllClients(new GetAllClients());
}
private function loadClientsCompleted(event:GetAllClientsResultEvent):void{
// 从返回的结果中获取客户端
clients=event.result as ArrayCollection;
}
private function loadClientsFault(event:FaultEvent):void{
// 显示错误消息
txtMsgErreur.text=event.fault.message;
}
private function displayClient(client:Client):String{
// 显示一个客户
return client.nom + " " + client.prenom;
}
]]>
</mx:Script>
<mx:Label text="Liste des clients" fontSize="14"/>
<mx:List dataProvider="{clients}" labelFunction="displayClient"></mx:List>
<mx:Button label="Afficher les clients" click="loadClients()"/>
<mx:Text id="txtMsgErreur" width="454" height="75"/>
- 第 9 行:ws 将指定类型为 WsDaoJpaService 的 C 代理,即之前生成的、实现远程 Web 服务访问方法的类。
- 第 13 行:当视图被实例化时(第 1 行),将执行方法 init
- 第15行:创建了一个C代理实例
- 第 17 行:为事件“异步方法 GetAllClients 已成功完成”关联了一个事件处理程序。 对于远程 Web 服务中的任何 m 方法,C 代理都会实现一个 addmEventListener 方法,该方法允许将处理程序关联到“异步方法 m 已成功完成”事件。 此处,第 17 行表示当 Flex 客户端收到客户端列表时,应执行第 26 行中的 loadClientsCompleted 方法。
- 第 18 行:为“C 代理的异步方法以失败结束”事件关联了一个事件处理程序。 此处第18行表示,每当C代理向Web服务S发出的异步请求失败时,就应执行第31行的方法loadClientsFault。此处唯一会发出的请求是获取客户端列表的请求。
- 最终,第13行的方法init实例化了C代理,并为后续将发出的异步请求定义了事件处理程序。
- 第21行:点击按钮[Afficher les clients](第44行)时执行的方法
- 第 23 行:执行代理 C 的异步方法 getAllClients。向其传递一个 GetAllClients 实例,该实例负责封装被调用的远程方法的参数。此处没有参数。 创建了一个空实例。方法 getAllClients 是异步的。执行过程会继续,而不会等待服务器返回的数据。用户可以继续与视图进行交互。用户触发的事件将继续被处理。通过方法 init,我们知道:
- 当 Flex 客户端收到客户列表时,将执行方法 loadClientsCompleted(第 26 行)
- 如果请求以错误结束,则将执行方法 loadClientsFault(第 31 行)。
- 第 28 行:在事件中获取客户列表。我们知道远程 Web 服务的 getAllClients 方法会返回一个列表。我们将该列表放入第 11 行的 clients 字段中。此处需要进行类型转换。 由于第43行的列表与“客户”字段建立了绑定(Bindable),该字段会收到数据变更的通知。随后,它将显示客户列表。它将使用方法displayClient(第43行)显示列表clients中的每个元素。
- 第36行:方法displayClient接收类型Client。它必须返回列表应为该客户显示的字符串。此处为姓名(第38行)。
- 第 31 行:当 Web 服务请求失败时执行的方法。它接收一个类型为 FaultEvent 的参数。 该类有一个 fault 字段,用于封装服务器返回的错误。fault.message 是随错误返回的错误信息。
- 第 33 行:错误消息显示在专门用于此目的的文本框中。
应用程序构建完成后,其可执行代码位于 Flex 项目的 [bin-debug] 文件夹中:
![]() |
上文所述,
- 文件 [rdvmedecins01.html] 代表浏览器将向 Web 服务器请求的 HTML 文件,以获取 Flex 客户端
- 文件 [rdvmedecins01.swf] 是 Flex 客户端的二进制文件,它将被封装在发送给浏览器的 HTML 页面中,随后由浏览器的 Flash Player 插件执行。
现在我们可以准备运行 Flex 客户端了。但在那之前,我们需要先搭建其所需的运行环境。让我们回到之前测试过的客户端/服务器架构:
![]() |
服务器端:
- 启动 SGBD MySQL
- 启动 Glassfish 服务器
- 如果尚未部署,请部署日程安排 Web 服务 JEE
- 如有必要,测试其中一个先前客户端,以验证服务器端是否一切就绪。
客户端:
启动将处理 Flex 应用程序请求的 Apache 服务器。此处我们使用工具 Wamp。借助该工具,我们可以为 Flex 项目的 [bin-debug] 文件夹分配一个别名。
![]() |
- Wamp图标位于屏幕底部 [1]
- 左键单击图标 Wamp,选择选项 Apache [2] / 别名目录 [3, 4]
- 选择选项 [5]:添加别名
![]() |
- 在 [6] 中为即将运行的 Web 应用程序指定一个别名(任意名称)
- 在 [7] 中指定将使用该别名的 Web 应用程序的根目录:即我们刚刚构建的 Flex 项目的 [bin-debug] 文件夹。
回顾一下 Flex 项目中 [bin-debug] 文件夹的结构:
![]() |
文件 [rdvmedecins01.html] 是 Flex 应用程序的 HTML 文件。借助我们刚刚在文件夹 [bin-debug] 上创建的别名,可以通过 URL [http://localhost/rdvmedecins/rdvmedecins01.html] 访问该文件。 我们将在安装了 Flash Player 9 或更高版本插件的浏览器中访问该网址:
![]() |
- 在 [1](即 Flex 应用程序的 URL)
- 在 [2] 中,我们请求客户列表
- 在 [3] 中,显示一切正常时的结果
- 在 [4] 中,当 Web 服务已停止运行时请求显示客户所获得的结果。
您可能想查看收到的 HTML 页面的源代码
页面主体从第 39 行开始。它不包含经典的 HTML 代码,而是一个类型为 "application/x-shockwave-flash"(第 60 行)的对象(第 47 行)。 这就是文件 [rdvmedecins01.swf](第 54 行),可在 Flex 项目的 [bin-debug] 文件夹中看到。这是一个体积较大的文件:仅此简单示例就约有 600 KB。
6.2. 第二个 Flex 客户端
第二个 Flex 客户端将不使用为第一个客户端生成的 C 语言代理。我们想说明,尽管该步骤相对于接下来将介绍的方法具有优势,但它并非必不可少。
项目演变如下:
![]() |
- 演变为 [1](新的 Flex 应用程序)
- 变为 [2] 及其关联的可执行文件
- [3] 新的视图:我们将显示医生列表。
应用程序 [rdvmedecins02.mxml] 的代码 MXML 如下:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:Script>
<![CDATA[
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;
import mx.collections.ArrayCollection;
// 数据
[Bindable]
private var medecins:ArrayCollection;
private function loadMedecins():void{
// 请求医生列表
wsrdvmedecins.getAllMedecins.send();
}
private function loadMedecinsCompleted(event:ResultEvent):void{
// 获取医生
medecins=event.result as ArrayCollection;
}
private function loadMedecinsFault(event:FaultEvent):void{
// 显示错误信息
txtMsgErreur.text=event.fault.message;
}
// 显示某位医生
private function displayMedecin(medecin:Object):String{
return medecin.nom + " " + medecin.prenom;
}
]]>
</mx:Script>
<mx:WebService id="wsrdvmedecins"
wsdl="http://localhost:8080/serveur-webservice-ejb-dao-jpa-hibernate/WsDaoJpaService?wsdl">
<mx:operation name="getAllMedecins"
result="loadMedecinsCompleted(event)" fault="loadMedecinsFault(event);">
<mx:request/>
</mx:operation>
</mx:WebService>
<mx:Label text="Liste des médecins" fontSize="14"/>
<mx:List dataProvider="{medecins}" labelFunction="displayMedecin"></mx:List>
<mx:Button label="Afficher les médecins" click="loadMedecins()"/>
<mx:Text id="txtMsgErreur" width="300" height="113"/>
</mx:Application>
我们仅对新增内容进行说明:
- 第 42 至 45 行:新视图。它与之前的视图完全相同,只是经过调整以显示医生而非客户。
- 第35-41行:此处的Web服务由<mx:WebService>标签(第35行)描述。此处不再使用上一版本中使用的C代理。
- 第 35 行:id 属性为 Web 服务命名。
- 第 36 行:wsdl 属性指定了 Web 服务中 WSDL 文件的 URI。该 URI 与前一个客户端使用的 URI 相同,并在第 4.10.2 节中进行了定义。
- 第 37-40 行:通过 <mx:operation> 标签定义远程 Web 服务的某个方法
- 第 37 行:引用的方法由 name 属性定义。此处我们引用了远程方法 getAllMedecins。
- 第 38 行:定义操作成功时(result 属性)和失败时(fault 属性)要执行的方法。
- 第 39 行:<mx:request> 标签用于定义操作的参数。 此处远程方法 getAllMedecins 没有参数,因此我们不填写任何内容。对于接受参数的方法 param1 和 param2,应写为:
其中 param1 和 param2 是声明并初始化在 <mx:Script> 标签中的变量
在 <mx:Script> 标签中,我们可以看到与前一个客户端中分析过的代码类似的 ActionScript 代码。 唯一不同的是第13至16行中的方法 loadMedecins。区别在于调用远程方法 [getAllMedecins] 的方式:
- 第15行:使用第35行定义的Web服务[wsrdvmedecins]及其第37行定义的操作[getAllMedecins]。为执行该操作,使用了send方法。 正是该方法启动了对第 35 行定义的 Web 服务中 getAllMedecins 方法的异步调用。 方法 send 将使用第 39 行 <mx:request> 标签定义的参数进行调用。此处没有参数。 如果该方法包含参数 param1 和 param2,则脚本 loadMedecins 会在调用方法 send 之前为这些参数赋值。
现在我们只需测试这个新应用程序:
![]() |

















