Spring Actionscript IOC 框架与 Flex Cairngorm MVC 应用
public interface IApplicationModel extends IEventDispatcher { function get productItems():ArrayCollection; [Bindable(event="productItemsChanged")] function set productItems(value:ArrayCollection):void; }
?然后让你的具体的 ModelLocator 类现这个接口,就像下面的代码:
清单 2.
public class ApplicationModel extends EventDispatcher implements IModelLocator, IApplicationModel{ private static var _modelInstance : IApplicationModel; // 单例, 用以返回 model 实例 public static function getInstance():IApplicationModel { if (!_modelInstance) { _modelInstance = new ApplicationModel(); } return _modelInstance; } public function ApplicationModel():void { super(); if (_modelInstance) { throw new Error('Only one ApplicationModel instance should be instantiated'); } } private var _productItems:ArrayCollection; public function get productItems():ArrayCollection{ return _productItems; } [Bindable(event="productItemsChanged")] public function set productItems(value:ArrayCollection):void{ if (value !== _productItems) { _productItems = value; dispatchEvent(new Event("productItemsChanged")); } } }
?
现在我们将它配置到我们的 Spring ActionScript IOC 容器中,
?
<object id="appModelInstance" factory-method="getInstance" scope="singleton"/>
?现在我们用 IoC 容器来管理我们的 Model 类的创建,我们的代码里不需要 getInstance() 工厂方法了,当你使用了 scope="singleton" 属性,IoC 容器内会确保只有一个 Model 实例被创建。所有我们的 Model 类可以被重构为:
清单 3.
public class ApplicationModel extends EventDispatcher implements IModelLocator, IApplicationModel { public function ApplicationModel():void { super(); } private var _productItems:ArrayCollection; public function get productItems():ArrayCollection { return _productItems; } [Bindable(event="productItemsChanged")] public function set productItems(value:ArrayCollection):void { if (value !== _productItems) { _productItems = value; dispatchEvent(new Event("productItemsChanged")); } } }
?然后修改我们的配置文件,去掉工厂方法的配置:
?
<object id="applicationModel" scope="singleton"/>
?就是这么简单,利用 Spring Ioc 容器使得我们的 Model 层的代码更加简洁。
?
FrontController
?
用过 Cairngorm 都知道 FrontController 类的重要,它是 MVC 框架中重要的中央控制器,它负责将我们的事件和我们的 Command 执行层绑定起来。在使用 Cairngorm 框架的时候,如果没有 IoC 容器管理,我们需要将 FrontController 类和 ServiceLocator 类(稍后会提到)添加到你的主应用程序的 MXML 类中,从而编译到最终的 Swf 文件里。就像下面的代码:
清单 4.
<mx:Application xmlns:control="com.cairngormclasses.control.*" xmlns:business="com.cairngormclasses.business.*" xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <control:Controller id="frontController"/> <business:Services id="serviceLocator"/> </mx:Application>
?在你的 FrontController 类中绑定事件和 commands 之后,FrontController 类会变得像下面一样:
清单 5.
public class Controller extends FrontController { public static const SOME_ACTION:String = "SomeActionEventID"; public function Controller() { super(); initializeCommands(); } // 将事件和 command 绑定起来 public function initializeCommands():void { addCommand( Controller.SOME_ACTION, SomeActionCommand ); } }
?这样的代码是可以运行的,但是将事件和 command 类的绑定关系写到 controller 类里面带来的问题是,如果需要改变这种绑定关系,就需要改写这个 Controller 类然后重新编译应用。于是 Spring Actionscript 框架提供了 CairngormFrontController,这个类允许将事件的 ID 和 Command 在 IoC 容器的配置文件中绑定起来。比如下面的配置:
清单 6.
<object id="frontController" scope="singleton"> <constructor-arg> <object> <!-- Add a new property for each event/command mapping: --> // 绑定事件和 command <property name="SomeActionEventID" value="SomeActionCommand"/> </object> </constructor-arg> <!-- The following argument is optional --> // 指明 commad 类所在的包路径 <constructor-arg value="com.mycommands.command"/> </object>
?第一个参数是个对象,它包含事件 ID 和 Command 类的关联,第二个参数是指明 Command 类的包名。通过这样配置之后,我们就不再需要 FrontController 类了,但是我们还是必须将 Command 类编译到应用程序的 Swf 文件内用于运行期的动态反射
?
这样之后,我们之前的 FrontController 和 ServiceLocator 类就不需要在应用中声明了:
清单 7.
<mx:Application xmlns:control="com.cairngormclasses.control.*" xmlns:business="com.cairngormclasses.business.*" xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> // 删除 FrontController 和 serviceLocator 的声明 <!-- control:Controller id="frontController"/ --> <!-- business:Services id="serviceLocator"/ --> </mx:Application>
?ServiceLocator
?
现在我们来看看 ServiceLocator 类,在源代码里配置远程服务不是很方便,而且当在开发时期和运行时期的远程服务路径不一致的时候就会带来一些麻烦,任何改变都需要重新编译应用程序。所以 当引入 Spring Actionscript 之后,这些配置的改变就可以抽到 xml 配置文件里,任何改动都不要重新编译了。Spring Actionscript 提供的 CairngormServiceLocator 能够支持在 IoC 容器中配置服务,例如:
清单 8.
<object id="serviceLocator" factory-method="getInstance"> <!-- The CairngormServiceLocator is marked with the dynamic keyword, //so new properties can be added at runtime: --> // 用于指定需要用的 service <property name="testService"> <ref> testService</ref> </property> </object>
?
<object id="testService" scope="singleton"> <property name="id" value="testService"/> <property name="endpoint" value="/flex2gateway/"/> <property name="destination="ColdFusion"/> <property name="source" value="com.myremoteobjects.someservice"/> <property name="showBusyCursor" value="true"/> <property name="makeObjectsBindable" value="true"/> </object>
?
Command 工厂
?
FrontController 通常用来实例化 Command 类,并将它绑定到事件上,在 Command 中,如果需要访问 Model 实例,通常的做法是直接调用 applicationModel.getInstance() 方法。这就是一种紧耦合的实现方式,而且这也会使得单元测试更加困难,所以如何在 Command 中注入别的实例,例如 Model,Spring Actionscript 提供了 IcommandFactory 接口去解决这个问题,这个接口非常简单,只需要实现以下两个方法:
?
public class CommandBase extends AbstractResponderCommand implements IApplicationModelAware { public function CommandBase() { super(); } private var _applicationModel:IApplicationModel; function get applicationModel():IApplicationModel { return _applicationModel; } // 用于注入 model function set applicationModel(value:IApplicationModel):void { _applicationModel = value; } // 执行方法 override public function execute(event:CairngormEvent):void { super.execute(event); } // 成功回调函数,需要被子类覆盖 override public function result(data:Object):void { throw new Error("Not implemented in base class"); } // 失败回调函数,需要被子类覆盖 override public function fault(info:Object):void { throw new Error("Not implemented in base class"); } }
?下面我们去创建 Command 工厂类,它负责创建 Command 的子类,以及注入 model 的引用。
清单 11.
public class ApplicationModelAwareCommandFactory implements ICommandFactory, IApplicationModelAware { // 用于注入 model private var _applicationModel:IApplicationModel; function set applicationModel(value:IApplicationModel):void { _applicationModel = value; } public function canCreate(clazz:Class):Boolean { return (ClassUtils.isSubclassOf(clazz, CommandBase)); } public function createCommand(clazz:Class):ICommand { var result:CommandBase = new clazz(); result.applicationModel = _applicationModel; return result; } }
?我们看到 ApplicationModelAwareCommandFactory 实现了 IApplicationModelAware 接口,现在为了能够将 model 注入到我们的工厂类中,我们需要在 Spring Actionscript 容器中进行配置,首先配置 command 工厂:
清单 12.
<object id="appAwareCmdFactory" scope="singleton"> <property name="applicationModel" ref="appModelInstance"/> </object>
?接下来,我们需要将这个 command 工厂类也注入到我们的 controller 中:
?
<object id="frontController" scope="singleton"> <constructor-arg> <object> <property name="SomeActionEventID" value="SomeActionCommand"/> </object> </constructor-arg> <!-- The followoing argument is optional --> <constructor-arg value="com.mycommands.command"/> <!-- Add a custom command factory instance --> // 配置 command 工厂 <method-invocation name="addCommandFactory"> <arg><ref>appAwareCmdFactory</ref></arg> </method-invocation> </object>
?现在,每一个添加到 FrontController 中的 command, 都会自动注入了 ApplicationModel 的引用,其他的注入类似。
?
Delegate 工厂
?
熟悉 Cairngorm 就会知道,delegate 类是用来和后台应用交互,发送以及接受数据用的,往往在 command 中需要引用 delegate 来完成完整的业务逻辑。我们在 command 类中创建具体需要的 delegate 类的实例,就像下面的例子,直接创造具体的 delegate:
清单 13.
public class GetProductItemsCommand extends CommandBase { private var _delegate:GetProductItemsDelegate; public function GetProductItemsCommand() { super(); //create the specific delegate class and pass ourselves as its responder // 创建具体的 delegate 对象 _delegate = new GetProductItemsDelegate(this as IResponder); } override public function execute(event:CairngormEvent):void { super.execute(event); //execute the delegate logic _delegate.getProductItems(); } override public function result(data:Object):void { // Business logic to handle the data retrieval } override public function fault(info:Object):void { // Logic to handle errors } }
?这样带来的问题就是我们将 command 和 delegate 又牢牢地绑定在了一起。如果需要改变使用的 delegate,就会带来一些不方便,而且需要重新编译。 基于这种问题,Spring Actionscript 提供了一个接口叫 IBusinessDelegate,我们的 delegate 例子继承这个接口:
?
public interface IGetProductItemsDelegate extends IBusinessDelegate { function getProductItems():void; }
?这个接口的实现可以继承 Spring Actionscript 提供的 AbstractBusiesssDelegate 类
清单 14.
?
public class GetProductItemsDelegate extends AbstractBusinessDelegate implements IGetProductItemsDelegate { public function GetProductItemsDelegate(service:* = null, responder:IResponder = null) { super(service, responder); } public function getProductItems():void { var token:AsyncToken = service.getProductItems(); token.addResponder(this.responder); } }
?
现在是时候去重构我们的 command 类了,我们的 command 类已经继承了 AbstractResponderCommand 类,这个基类已经包含了一个叫做 businessDelegate 的属性,我们不再需要手工去创建 delegate 的实例,我们只需要将需要的 delegate 注入进来,然后将 businessDelegate 属性转化成我们需要的 delegate 接口。
清单 15.
public class GetProductItemsCommand extends CommandBase { public function GetProductItemsCommand() { super(); } override public function execute(event:CairngormEvent):void { super.execute(event); // 通过 businessDelegate 属性获得注入的 delegate 对象 IGetProductItemsDelegate(this.businessDelegate).getProductItems(); } override public function result(data:Object):void { // Business logic to handle the data retrieval } override public function fault(info:Object):void { // Logic to handle errors } }
?接下里我们看如何将这个 delegate 注入到被 command 工厂创建出来的 command 中,Spring actionscript 提供了一个叫做 RespondCommandFactory 的类去完成这个工作,它提供了一个叫做 addBusinessDelegateFactory 的方法:需要将我们的 command 工厂继承它:
清单 16.
public class ApplicationModelAwareCommandFactory extends ResponderCommandFactory implements IApplicationModelAware { }
?最后,我们需要配置到 IOC 配置文件中,我们首先来配置 BusinessDelegate 工厂:
清单 17.
<object id="businessDelegateFactory" scope="singleton"> <property name="service" ref="testService"/> <property name="delegateClass" type="class" value="com.myclasses.delegates.GetProductItemsDelegate"></property> </object>
?然后我们需要将这个 delegate 工厂和我们的 command 工厂联系起来:
清单 18.
<object id="appAwareCmdFactory" scope="singleton"> <property name="applicationModel" ref="appModelInstance"/> <method-invocation name="addBusinessDelegateFactory"> <arg> <ref>businessDelegateFactory</ref> </arg> <arg> <array> <value type="Class">com.myclasses.commands.GetProductItemsCommand</value> </array> </arg> </method-invocation> </object>
?现在,我们基于 Cairngorm MVC 的应用都通过 Spring Actionscript 容器变得松散耦合起来了,今后很多的重构都只需要改改配置文件,Flex 应用的可维护性和可扩展性都得到了大大的提高。
安装配置开发环境
开始前先让我们来搭建我们的开发环境:
下载并安装JDK( 本文使用版本为 Sun JDK 6)
下载并解压Eclipse( 本文使用版本为 Eclipse Ganymede J2EE SR2 版本 )
下载并安装FB3_WWEJ_Plugin.exe(Flex Builder plug-in for eclipse)
?
安装 FB3_WWEJ_Plugin.exe 过程中选中 eclipse 安装目录。
一个入门的小例子
?
前面我们讲解了 Spring Actionscript 如何与 Cairngorm MVC 框架一起使用,下面我用一个简单的小例子来进一步的理解开发过程,通过以下代码示例,就会理解如何配置以及使用 Spring Actionscript IOC 容器。
下载并解压 Spring Actionscript解压出来主要包括以下文件:
spring-Actionscript-core-1.0RC1.swc (核心类)
spring-Actionscript-cairngorm-1.0RC1.swc (与 cairngorm mvc 框架集成)
依赖库:
as3commons-lang.swc
as3commons-logging.swc
as3commons-reflect.swc
下载并解压 Cairngorm 2.2解压出来就是一个 Cairngorm.swc 文件
新建 Flex Project点击 Finish,将以下 swc 拷贝到项目的 flex_libs 目录下
?
在 flex_src 目录下建好包结构,创建 application-context.xml 用于配置对象以及之间的依赖关系。
?
这个配置文件用于配置以上介绍的扩展点,比如 ModelLocator,FrontController,Command 工厂等。
清单 19. 配置文件
<?xml version="1.0" encoding="utf-8"?> <objects xmlns="http://www.springactionscript.org/schema/objects" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:si="http://www.springactionscript.org/schema/stageinterception" xsi:schemaLocation="http://www.springactionscript.org/schema/objects http://www.springactionscript.org/schema/objects/spring-actionscript-objects-1.0.xsd"> <! — modelLocator 的配置 --> <object id="appModelLocator" scope="singleton"/> <!-- front controller 的配置 --> <object id="frontController" value="commands.AddPersonCommand" /> <property name="LoadPersons" value="commands.LoadPersonCommand" /> </object> </constructor-arg> <! —配置创建上述 command 的工厂 --> <method-invocation name="addCommandFactory"> <arg><ref>appAwareCmdFactory</ref></arg> </method-invocation> </object> <! — service locator 的配置 --> <object id="serviceLocator" scope="singleton"> <property name="url" value="xml/peopleinfo.xml"/> </object> <!--Delegate 工厂的配置 --> <object id="businessDelegateFactory" scope="singleton"> <property name="service" ref="personInfosService"/> <property name="delegateClass" type="class" value="business.delegates.PersonDelegate"></property> </object> <! — Command 工厂 --> <object id="appAwareCmdFactory" ref="appModelLocator" /> // 在 command 中注入的 delegate <method-invocation name="addBusinessDelegateFactory"> <arg> <ref>businessDelegateFactory</ref> </arg> <arg> <array> <value type="Class">commands.LoadPersonCommand</value> </array> </arg> </method-invocation> </object></objects>
?
?
创建 MyModelLocator.as 文件,用于注入 model 实例。
清单 20. MyModelLocator.as
public class MyModelLocator extends EventDispatcher implements IModelLocator, IPeopleModel { public function MyModelLocator() { } public function get personInfos():ArrayCollection { return _personInfos; } public function set personInfos(value:ArrayCollection):void { _personInfos = value; } private var _personInfos:ArrayCollection = new ArrayCollection(); }
?创建 Command Factory 文件,用于动态创建 Command,并将 model 以及 delegate 注入到 Command 中。
清单 21. ApplicationModelAwareCommandFactory.as
public class ApplicationModelAwareCommandFactory extends ResponderCommandFactory implements ICommandFactory, IApplicationModelAware { private var _applicationModel:IPeopleModel; public function ApplicationModelAwareCommandFactory() { uper(); } //method in ICommandFactory, the result of this method determine //whether the requested command can be created override public function canCreate(clazz:Class):Boolean { if (ClassUtils.isSubclassOf(clazz, CommandBase)) { return super.canCreate(clazz); } return false; } //method in ICommandFactory, used to create command override public function createCommand(clazz:Class):ICommand { var result:CommandBase = super.createCommand(clazz) as CommandBase; result.applicationModel = _applicationModel; return result; } public function set applicationModel(value:IPeopleModel):void { applicationModel = value; } }
?创建具体的 command 子类,用于处理具体的业务逻辑。
清单 22. LoadPersonCommand.as
public class LoadPersonCommand extends CommandBase { public function LoadPersonCommand() { super(); } override public function execute(event:CairngormEvent):void { // 获得注入的 delegate 对象 super.execute(event); IPersonDelegate(this.businessDelegate).getPersonInfos(); } // 成功回调函数 override public function result(data:Object):void { //httpservice 接受返回的数据 } // 失败回调函数 override public function fault(data:Object):void { Alert.show("load people info failed"); } }
?创建具体的 delegate 类用于处理具体的业务逻辑。
清单 23. PersonDelegate
public class PersonDelegate extends AbstractBusinessDelegate implements IPersonDelegate { public function PersonDelegate(service:* = null,responder:IResponder = null) { super(service, responder); } // 发送请求 public function getPersonInfos():void { //send request var token:AsyncToken = this.service.send(); token.addResponder(this.responder); } }
?
图 3. 示例程序结构:
图 4. 运行结果
具体实现细节请读者参考代码示例,这里仅仅实现了加载数据和添加数据的功能,删除和修改功能留给读者自己去实现。
结束语
?
本文对 Flex IOC 框架 Spring Actionscript 如何与 Cairngorm MVC 框架结合到一起做了一个细致的介绍,当然还有一些细节问题值得继续研究,相信通过本文的介绍,可以帮助您更好的掌握这两种框架,从而在 Flex 应用开发中充分发挥它们带给我们的优点。
所需软件
?
下载并安装JDK( 本文使用版本为 Sun JDK 6) 下载并解压Eclipse( 本文使用版本为 Eclipse Ganymede J2EE SR2 版本 ) 下载并安装FB3_WWEJ_Plugin.exe(Flex Builder plug-in for eclipse) SpringActionscript Cairngorm 2.2