原文链接 http://coenraets.org/blog/2009/07/building-a-flex-application-with-the-parsley-framework/
在最近研究了Swiz和SprintActionScript(1,2,3)之后,我决定再试用一下Parsley framework,创建Parsley版本的inSync,这是一个简单的管理联系人的应用,我经常用这个作为demo演示Flex和Adobe AIR的特性和技巧
Parsley首先是一个依赖注入的framework。它还提供了一种有趣的消息机制作为基础。依赖注入就不多说了,之前Swiz和Spring ActionScript的文章中有一些背景资料和很多细节作为参考。
配置 Object
Parsley让你有多种方式配置核心的objects(其他的组件依赖于这些对象),使用MXML(像Swiz),使用XML(像Spring ActionScript)或者ActionScript。或者各种方式的组合。甚至可以扩展framework,创造你自己的配置机制
就inSyc而言,我选择了MXML的方式(Config.mxml),如下
<?xml version="1.0" encoding="utf-8"?> <Object xmlns="*" xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:services="insync.parsley.services.*"> <mx:RemoteObject id="contactRO" destination="contacts" endpoint="http://localhost:8400/samples/messagebroker/amf" showBusyCursor="true" /> <services:ContactService /> </Object>
注,在实际项目中,你并不应该hardcode service的endpoint。使用XML在外部(更外了)来配置endpoint。这里有更多相关的讨论
初始化Framework
你选择了那种类型的配置方式,就通过与之对应的ContextBuilder来初始化framework。下面是inSync的Main文件。我让Parsley在preinitialize event中初始化。
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:views="insync.parsley.views.*" paddingTop="0" paddingLeft="0" paddingRight="0" paddingBottom="0" preinitialize="FlexContextBuilder.build(Config)"> <mx:Script> <![CDATA[ import org.spicefactory.parsley.flex.FlexContextBuilder; ]]> </mx:Script> <mx:Style source="styles.css" /> <views:MainView /> </mx:Application>
依赖注入
Parsley支持构造函数注入,方法注入和属性注入(指定type和指定id)
下面的例子(ContactForm.mxml),framwork将注入IContactService的具体实现,实例是由Config.mxml创建的ContactService,通过指定type注入。针对接口编程,让view对IContactService的具体实现解耦。
注:Parsley并不强制你使用特定的设计模式。就这个简单的sample而言,我直接把Service注入到view。使用接口一定程度的做到了解耦,你当然还可以使用其他的模式(比如Presentation Model)来架构更高层的抽象。Parsley的依赖注入和消息机制让应用这些模式更加容易。
<?xml version="1.0" encoding="utf-8"?> <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" addedToStage="dispatchEvent(new Event('configureIOC', true))"> <mx:Script> <![CDATA[ import insync.parsley.model.Contact; import insync.parsley.services.IContactService; [Inject] public var service:IContactService; [Bindable] public var contact:Contact; private function save():void { contact.firstName = firstName.text; contact.lastName = lastName.text; contact.email = email.text; service.save(contact); } private function remove():void { service.remove(contact); } ]]> </mx:Script> <mx:Form> <mx:FormItem label="Id"> <mx:TextInput text="{contact.id}" enabled="false"/> </mx:FormItem> <mx:FormItem label="First Name"> <mx:TextInput id="firstName" text="{contact.firstName}"/> </mx:FormItem> <mx:FormItem label="Last Name"> <mx:TextInput id="lastName" text="{contact.lastName}"/> </mx:FormItem> <mx:FormItem label="Email"> <mx:TextInput id="email" text="{contact.email}"/> </mx:FormItem> </mx:Form> <mx:HBox left="12" bottom="12"> <mx:Button label="Save" click="save()"/> <mx:Button label="Delete" click="remove()"/> </mx:HBox> </mx:Canvas>
注意到,view为了得到注入进来依赖对象,在被添加到stage的时候,view必须dispatch "configureIOC"事件
消息
Parsley提供通用的消息机制,允许在对象之间松耦合,灵活和易用的传递消息:
event的dispatcher和handler不知道彼此
event的dispatcher和handler和framework之间也是松耦合的,通过Flex的方式dispatch事件
我们来看一下inSync中Search这一块儿。
ToolBar中有一个TextInput让用户输入查找条件
ContactList在DataGrid中显示查到的结果
Toolbar如下(去掉了逻辑不相关的代码以清晰)
<?xml version="1.0" encoding="utf-8"?> <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" addedToStage="dispatchEvent(new Event('configureIOC', true))"> <mx:Metadata> [ManagedEvents("search")] </mx:Metadata> <mx:Script> <![CDATA[ import insync.parsley.events.SearchEvent; ]]> </mx:Script> <mx:Label text="Search:" top="18" right="164"/> <mx:TextInput id="searchBox" right="0" top="16" change="dispatchEvent(new SearchEvent(SearchEvent.SEARCH, searchBox.text))"/> </mx:Canvas>
注意看input的内容change的时候dispatch SearchEvent事件。
使用ManagedEvents MetaData定义这些个event是被framework管理的,意思说framework将保证到时候这些事件会被对其感兴趣的部分正确的收到。
怎么注册被framework管理的event的listener呢?我们在给function加注[MessageHandler]Metadata,这个函数就自动变成了对应事件的handler,不管显示列表结构,或者事件冒泡与否。
ContactList如下(去掉了不相关的代码)
<?xml version="1.0" encoding="utf-8"?> <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%" addedToStage="dispatchEvent(new Event('configureIOC', true))"> <mx:Script> <![CDATA[ import mx.controls.Alert; import mx.collections.ArrayCollection; import mx.rpc.events.FaultEvent; import mx.rpc.AsyncToken; import mx.rpc.AsyncResponder; import mx.rpc.events.ResultEvent; import insync.parsley.events.SearchEvent; import insync.parsley.services.IContactService; [Inject] [Bindable] public var service:IContactService; [Bindable] public var contacts:ArrayCollection; [MessageHandler] public function searchHanlder(event:SearchEvent):void { service.getContactsByName(searchStr).addResponder(new AsyncResponder(getContacts_result, faultHandler)); } private function getContacts_result(event:ResultEvent, token:AsyncToken):void { contacts = event.result as ArrayCollection; } private function faultHandler(event:FaultEvent):void { Alert.show(event.fault.faultString); } ]]> </mx:Script> <mx:DataGrid id="dg" dataProvider="{contacts}" width="100%" top="0" left="0" right="0" bottom="0"/> </mx:Canvas>
单单使用[MessageHandler] ,在Application范围,每次和handler参数相同类型的事件被dispatch的时候,handler都会被调用。为了更好的控制handler该被调用,可以指定selector为某些特定的event type。比如MainView.mxml,contactDeleted函数被注册为只监听ContactEvent.DELETED事件的handler,如下:
[MessageHandler(selector="contactDeleted")] public function contactDeletedHandler(event:ContactEvent):void { }
Parsley还有更多的消息使用方法。比如可以用[MessageBinding]来bind一个对象的属性到一个事件的属性:
[MessageBinding(messageProperty="result",type=" mx.rpc.events.ResultEvent")] public var contacts:ArrayCollection;
实例代码的安装调试
1,下载整个Project
2,导入到Flash Builder
3,运行application。这个application默认使用MockService(ContactServiceMock),所以不需要后台
如果需要远端service而不是MockService
1,替换<services:ContactServiceMock/>为<services:ContactServiceRemote/>
2,安装BlazeDS turnkey server
3,下载insync-parsley-java.zip 后解压到本地
4,复制classes/insync 到blazeds/tomcat/webapps/samples/WEB-INF/classes/insync
5,添加destination到blazeds/tomcat/webapps/samples/WEB-INF/flex/remoting-config.xml
<destination id="contacts"> <properties> <source>insync.dao.ContactDAO</source> <scope>application</scope> </properties> </destination>
6,复制insync/sampledb/insync 到blazeds/sampledb/insync
7,编辑blazeds/sampledb下的server.properties 按照下面的方式把inSync的database添加到启动项中
8,启动database (用startdb)
9,启动blazeDS
10,运行Application