Part 1基本概念
Spring AS框架是Java中Spring IoC框架的AS版本。它也是众多围绕依赖注入提供解决方案的Flex框架之一(还包括Swiz和Mate等框架).最近我花了一些时间研究”Spring ActionScript”。目前关于此框架的资源还不是很多,所以我想和大家分享下我的心得。
NOTE:为了不引起混淆,本该项目和我之前写过的Spring/BlazeDS整合项目是完全独立的(它们也可以很好的融合在一起)。:
在本文中,我将介绍一些基本的概念…想象一下如果你的应用程序使用RemoteObject访问后台,除非你的程序只是由少数几个组件构成,并且一直与相同的server交互,否则你迟早会遇到下面的俩个问题:
怎样在外部而不是在代码里面配置service?
怎样将程序的组件的引用提供给service?
NOTE:1.第一个例子有意的保持了简单。在更为细分的应用中,你也许只想提供一个更抽象的控制器而不是某个特定的RemoteObject。关于这点将在第三部分介绍。
2.Spring AS是一种控制反转框架:你可以不用RemoteObject,或任何数据访问策略,或甚至连接后台…这里我只是创建了一个测试用例.
对象的配置与装配
Spring AS使用xml文件来配置它的组件。它的优点是能够让你改变组件的配置,或切换组件的实现过程,而不需要重新编译,这样我们就会在产品的开发和测试过程中获得极大的灵活性。另一方面,当你在xml配置文件声明你的类时,编译器是不知道你在做什么的:组件的配置在编译时不会被检查,更为重要的是xml配置文件中定义的类如果在代码中没有被直接引用,它们是不会被链接到swf里去的。但无论如何这不会是主要的问题,因为我们可以告诉编译器去引用这些类。(参考第三部分)
在第一个例子中,我们将配置一个简单的RemoteObject对象…
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="utf-8"?>
<objects>
<object id="channelSet" class="mx.messaging.ChannelSet">
<method-invocation name="addChannel">
<arg>
<object id="amfChannel" class="mx.messaging.channels.AMFChannel">
<property name="url" value="http://localhost:8400/lcds-samples/messagebroker/amf"/>
</object>
</arg>
</method-invocation>
</object>
<object id="contactRemoteObject" class="mx.rpc.remoting.mxml.RemoteObject">
<property name="channelSet" ref="channelSet" />
<property name="destination" value="contacts" />
<property name="showBusyCursor" value="true" />
</object>
</objects>
注意我们首先配置一个ChannelSet对象。在此例中,我们定义了一个单独的AMF channel,之后配置我们需要的RemoteObject对象,并将我们之前配置的ChannelSet注入到RemoteObject对象中。
在外部配置RemoteObject和ChannelSet有很多好处…你可以不用重新编译而做以下事情:
1. 改变AMFChannel的URL,使其指向别的server,别的端口,别的endpoint
2. 给ChannelSet添加更多的候选channel
3. 改变channel的类型,例如把AMFChannel改成RTMPChannel
4. 改变目标destination。
作为一种更简单的选择,我们可以象下面这样配置RemoteObject对象:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version=”1.0″ encoding=”utf-8″?>
<objects>
<object id=”remoteObject” class=”mx.rpc.remoting.mxml.RemoteObject” abstract=”true”>
<property name=”endpoint” value=”http://localhost:8400/lcds-samples/messagebroker/amf” />
<property name=”showBusyCursor” value=”true” />
</object>
<object id=”contactRemoteObject” parent=”remoteObject”>
<property name=”destination” value=”contacts” />
</object>
</objects>
注意,为了避免重复,你可以配置一个伪的抽象对象(本例中即”remoteObject”)
在应用程序中获取配置的对象
现在RemoteObject已经配置好了,那就在程序中开始使用他们吧
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*"
initialize="applicationCompleteHandler()">
<mx:Script>
<![CDATA[
import mx.rpc.AsyncResponder;
import mx.controls.Alert;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.AsyncToken;
import mx.rpc.remoting.mxml.RemoteObject;
import org.springextensions.actionscript.context.support.FlexXMLApplicationContext;
private var applicationContext:FlexXMLApplicationContext;
private var remoteObject:RemoteObject;
private function applicationCompleteHandler():void
{
applicationContext = new FlexXMLApplicationContext("applicationContext.xml");
applicationContext.addEventListener(Event.COMPLETE, applicationContextLoaded);
applicationContext.load();
}
private function applicationContextLoaded(event:Event):void
{
remoteObject = applicationContext.getObject("contactRemoteObject");
var token:AsyncToken = remoteObject.getContacts();
token.addResponder(new AsyncResponder(resultHandler, faultHandler, token));
}
private function resultHandler(event:ResultEvent, token:AsyncToken):void
{
dg.dataProvider = event.result;
}
private function faultHandler(event:FaultEvent, token:AsyncToken):void
{
Alert.show(event.fault.faultString);
}
]]>
</mx:Script>
<mx:DataGrid id="dg" width="100%" height="100%"/>
</mx:Application>
使用Spring AS框架的第一步就是创建一个新的ApplicationContext去加载配置文件。当加载完成时,就可以通过ApplicationContext对象的getObject方法得到你配置的对象,之后就是和普通程序一样的逻辑了。
下一步
在这第一个例子里,我们看到了Spring ActionScript框架是怎样在外部通过配置文件来配置和分配对象的,也看到了怎样通过ApplicationContext对象的getObject方法来得到配置的对象。
接下来的第二步,我们将讨论怎样在view中以非紧耦合的方式得到这些配置好的对象,而非让这些对象通过层层的视图组件来传递。
Part 2 自动封装(AutoWiring)
在第一部分中我们介绍了“Spring AS”框架怎样帮助我们在外部配置和封装组件,并通过applicationContext.getObject方法来使用这些配置好的对象。
在第二部分里,我们将要讨论怎样让view以非紧耦合的方式使用这些对象,并且不用通过多层级的引用传递来传递这些对象的引用。
现在我们来创建基于“Spring AS”框架的”InSync contact management“程序。该程序包含2个view:Mainview和ContactForm。它们都需要使用RemoteObject对象的引用。
注意:.这个例子有意的保持了简单。在更为细分的应用中,你也许只想提供一个更抽象的控制器而不是某个特定的RemoteObject。关于这点将在第三部分介绍。
视图可以使用applicationContext.getObject()来访问它们所需要的依赖物(本例中指RemoteObject对象),但这种方式有一些问题:
因为依赖于applicationContext, view可能会和框架紧耦合在一起,但我们仍然需要将applicationContext对象的引用传给views,当然这种问题通常都是通过使用单例模式来解决的。更好的办法是使用注入方式将依赖关系注入到views中,而不是实例化视图让视图自己查找依赖关系。
与Swiz不同的是,“Spring AS”目前不支持内建的[Autowire]注释,但Christophe Herreman(SPRING ACTIONSCRIPT框架的创始人)曾今暗示该特性将来可能会出现,与此同时他还提供了一些简单的例子来说明“Spring ActionScript”框架的自动封装特性。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
applicationComplete="applicationCompleteHandler()">
<mx:Script>
<![CDATA[
import insync.views.MainView;
import mx.utils.DescribeTypeCacheRecord;
import mx.utils.DescribeTypeCache;
import as3reflect.ClassUtils;
import org.springextensions.actionscript.context.support.FlexXMLApplicationContext;
private var applicationContext:FlexXMLApplicationContext;
private function applicationCompleteHandler():void
{
applicationContext = new FlexXMLApplicationContext("applicationContext.xml");
applicationContext.addEventListener(Event.COMPLETE, applicationContextComplete);
applicationContext.load();
}
private function applicationContextComplete(event:Event):void
{
systemManager.addEventListener(Event.ADDED, addedEventHandler);
var mainView:MainView = new MainView();
addChild(mainView);
}
private function addedEventHandler(event:Event):void
{
var autowiredObject:Object = event.target;
trace("Added to display list: " + autowiredObject);
var typeInfo:DescribeTypeCacheRecord = DescribeTypeCache.describeType(autowiredObject);
for each (var metaDataNode:XML in typeInfo.typeDescription..metadata)
{
if (metaDataNode.attribute("name") == "Autowired")
{
var propertyNode:XML = metaDataNode.parent();
var property:String = propertyNode.@name.toString();
trace("Found Autowired property: " + property);
var objectName:String = property;
var autowireByType:Boolean = true;
for each (var arg:XML in metaDataNode.arg)
{
if (arg.attribute("value") == "byName")
{
autowireByType = false;
}
}
if (autowireByType)
{
var clazz:Class = ClassUtils.forName(propertyNode.@type.toString());
var objectNames:Array = applicationContext.getObjectNamesForType(clazz);
if (objectNames.length == 1)
{
objectName = objectNames[0];
}
}
trace("Autowiring: " + property + " in " + autowiredObject);
autowiredObject[property] = applicationContext.getObject(objectName);
}
}
}
]]>
</mx:Script>
<mx:Style source="styles.css"/>
</mx:Application>
为了能够通过Autowired注释达到注入属性的目标,我们给systemManager的ADDED事件添加一监听器, 在监听器中我们通过AS的内省机制来分析每个添加到显示列表的对象,如果对象含有[Autowired]属性,这些就会被注入相关的值。这种方式也是Swiz采用的方式。
时机
在注入对象到view之前,你必须确定applicationContext.xml文件被加载完成并且文件中定义的对象已经被实例化。applicationContext 在它准备好之后派发Event.COMPLETE事件,为了确保程序能正确的被注入属性,我一般都会在Event.COMPLETE事件的处理器中实例化MainView。
视图
如果上面的基础已经ok,InSync程序的视图就很容易编写了。
安装指南
注意,如果你已经安装了Swiz版本的InSync, 你可以直接跳过第1,3,4,5,6步。
1. 安装BlazeDS turnkey server.(澄清一下,你不用非得使用BlazeDS,只不过本例使用的是BlazeDS)
2. 下载insyncspringas.zip文件并解压缩.
3. 将insyncspringas/java/classes/insync目录拷贝到blazeds/tomcat/webapps/samples/WEB-INF/classes/insync目录
4. 向blazeds/tomcat/webapps/samples/WEB-INF/flex/remoting-config.xml文件添加一下代码:
<destination id=“contacts”>
<properties>
<source>insync.dao.ContactDAO</source>
<scope>application</scope>
</properties>
</destination>
5. 将insyncspringas/sampledb/insync目录拷贝到blazeds/sampledb/insync目录
6. 编辑blazeds/sampledb的server属性文件:
server.database.0=file:flexdemodb/flexdemodb
server.dbname.0=flexdemodbserver.database.1=file:insync/insyncserver.dbname.1=insyncserver.port=9002server.silent=trueserver.trace=false
7. 启动数据库(startdb.bat或startdb.sh)
8. 启动BlazeDS
9. 在Flex Builder中创建名为insyncspringas的工程(不用指定Application server type)
10. 将insyncspringas/flex/lib目录下的spring-actionscript.swc和as3reflect.swc拷贝到insyncspringas工程的lib目录下
11. 将insyncspringas/flex/src目录下的文件拷贝到insyncspringas工程的src下
12. 打开applicationContext.xml,确保remoteObject的endpoint属性与server的配置一致
13. 运行程序
自动封装及性能表现的注意事项
基于注释的依赖注入很简洁也很方便,但是我们需要使用describeType来操作每个显示对象,这样就会对性能有影响。也许Flex框架将来会改进这一点。比如我们可以在类实例时为每个Autowired属性派发AutowireEvent 事件,然后把它留给框架去实现,这样框架就只需要监听这些事件而不是对每个显示列表对象都进行内省。(An alternative would be to have a compiler hook to allow annotation-based code injection at compile time.有点不太明白)
同时,你必须确定当前的方案对程序性能的影响是否可以接受,如果否,有些方法可以改进:
1.使用Swiz, Aral Balkan在需要使用自动分配对象的视图中使用autowired属性,代码中,使用describType方法之前先用hasOwnproperty(“autowiring”)方法来来判断对象是否需要自动分配。但该方法在某种程度上还是需要对每个显示对象进行内省分析。
2. Swiz 现在不会对mx 包下对象使用describType方法进行分析。我们可以考虑此方法。
3.还有一种方式是,在类初始化时为需要自动分配对象的视图派发AutowiredEvent事件。这也许比上简单的注释方便,但至少不会产生负面的性能影响,并且此方法能让非显示对象也能使用自动分配的对象。
Part 3 注入service
在第一部分里,我们介绍了怎样利用SPRING ACTIONSCRIPT框架在程序外部设置程序的属性以及分配对象。在第二部份里,我们讨论了怎样对视图的属性自动装配。为了保持简单,我们直接将contact RemoteObject注入到了视图中。但一般在真实的场景下,人们不是想让视图和service的实现过程紧耦合在一起的。视图应该和数据访问策略(RemoteObject,HTTPService, WebService, mock service等)处于独立的关系。
为了满足上述需求, 我们将创建一个IContactService接口,以及2个实现该接口的类。
ContactRemoteObjectService 使用RemoteObject访问数据。
ContactMockService 在没有后台数据时,封装模拟数据用于测试
IContactService 定义如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package insync.services
{
import insync.model.Contact;
import mx.rpc.AsyncToken;
public interface IContactService
{
function getContactsByName(name:String):AsyncToken;
function save(contact:Contact):AsyncToken;
function remove(contact:Contact):AsyncToken;
}
}
ContactRemoteObjectService的实现:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package insync.services
{
import insync.model.Contact;
import mx.controls.Alert;
import mx.rpc.AsyncResponder;
import mx.rpc.AsyncToken;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.mxml.RemoteObject;
public class ContactRemoteObjectService implements IContactService
{
private var remoteObject:RemoteObject;
public function ContactRemoteObjectService(remoteObject:RemoteObject)
{
this.remoteObject = remoteObject;
}
public function save(contact:Contact):AsyncToken
{
var token:AsyncToken = remoteObject.save(contact);
token.contact = contact;
token.addResponder(new AsyncResponder(save_result, faultHandler));
return token;
}
public function remove(contact:Contact):AsyncToken
{
var token:AsyncToken = remoteObject.remove(contact);
token.addResponder(new AsyncResponder(remove_result, faultHandler));
return token;
}
public function getContactsByName(name:String):AsyncToken
{
var token:AsyncToken = remoteObject.getContactsByName(name);
token.addResponder(new AsyncResponder(getContactsByName_result, faultHandler));
return token;
}
/* Result Handlers ---------------------------------------------------------------*/
private function save_result(event:ResultEvent, token:AsyncToken=null):void
{
// For a create operation, assign the generated primary key to the id property
// of the contact object
event.token.contact.id = event.result.id;
// Dispatch an event on the async token. This allows the caller of the method to register as
// a listener for the result event of the specific method call. (See ContactForm.mxml for an example.
event.token.dispatchEvent(event);
}
private function remove_result(event:ResultEvent, token:AsyncToken=null):void
{
event.token.dispatchEvent(event);
}
private function getContactsByName_result(event:ResultEvent, token:AsyncToken=null):void
{
event.token.dispatchEvent(event);
}
private function faultHandler(event:FaultEvent, token:AsyncToken=null):void
{
Alert.show(event.fault.faultString + "\n" + event.fault.faultDetail, "Error Invoking RemoteObject");
}
}
}
Note:ContactRemoteObjectService类除了remoteObject属性是通过Spring AS框架注入的以外,其他代码跟普通的类没什么分别。实现过程和Flex framework特别是rpc API部分紧耦合在一起,我们也可以自己写这部分的实现。Spring AS还提供了抽象API用于引用异步方法,而且不是跟Flex framework耦合的(参考AbstractRemoteObjectService类)。Spring AS的目标是作为AS框架而非Flex框架被开发者使用。
ContactMockService 的实现:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package insync.services
{
import insync.model.Contact;
import mx.collections.ArrayCollection;
import mx.rpc.AsyncResponder;
import mx.rpc.AsyncToken;
import mx.rpc.events.ResultEvent;
public class ContactMockService extends MockService implements IContactService
{
private var contacts:ArrayCollection = new ArrayCollection();
private var nextId:int;
public function ContactMockService()
{
var contact:Contact = new Contact();
contact.id = 1;
contact.firstName = "Christophe";
contact.lastName = "Coenraets";
contacts.addItem(contact);
contact = new Contact();
contact.id = 2;
contact.firstName = "Lisa";
contact.lastName = "Taylor";
contacts.addItem(contact);
contact = new Contact();
contact.id = 3;
contact.firstName = "John";
contact.lastName = "Smith";
contacts.addItem(contact);
nextId = 4;
}
public function save(contact:Contact):AsyncToken
{
if (contact.id == 0) // New contact
{
contact.id = nextId++;
contacts.addItem(contact);
}
var token:AsyncToken = createToken(contact);
token.addResponder(new AsyncResponder(resultHandler, null));
return token;
}
public function remove(contact:Contact):AsyncToken
{
var result:ArrayCollection = new ArrayCollection();
for (var i:int=0; i<contacts.length; i++)
{
var current:Contact = contacts.getItemAt(i) as Contact;
if (current.id == contact.id)
{
contacts.removeItemAt(i);
break;
}
}
var token:AsyncToken = createToken(contacts);
token.addResponder(new AsyncResponder(resultHandler, null));
return token;
}
public function getContactsByName(name:String):AsyncToken
{
var result:ArrayCollection = new ArrayCollection();
for (var i:int=0; i<contacts.length; i++)
{
var contact:Contact = contacts.getItemAt(i) as Contact;
if (contact.fullName.indexOf(name)>=0)
{
result.addItem(contact);
}
}
var token:AsyncToken = createToken(result);
token.addResponder(new AsyncResponder(resultHandler, null));
return token;
}
private function resultHandler(event:ResultEvent, token:AsyncToken = null):void
{
event.token.dispatchEvent(event);
}
}
}
为了保持视图和service的独立性,我们给视图定义了接口类型的属性(IContactService).依靠Spring AS application context,指定的service实现类会被注入到视图中。如下:
ContactForm的定义
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:controls="insync.controls.*"
width="100%" height="100%"
label="{contact.id>0?contact.fullName:'New Contact'}">
<mx:Script>
<![CDATA[
import mx.rpc.events.ResultEvent;
import insync.events.ContactEvent;
import insync.services.IContactService;
import insync.model.Contact;
[Autowired]
public var contactService:IContactService;
[Bindable]
public var contact:Contact;
private function save():void
{
contact.firstName = firstName.text;
contact.lastName = lastName.text;
contact.email = email.text;
contact.phone = phone.text;
contact.address = address.text;
contact.city = city.text;
contact.state = state.text;
contact.zip = zip.text;
contact.pic = picture.source;
contactService.save(contact).addEventListener(ResultEvent.RESULT,
function(event:ResultEvent):void
{
// Display a status message. Added here to provide an example where
// a specific contact view needs to be notified of the success or failure
// of a service operation.
status.text = "Contact saved successfully";
setTimeout(function():void{status.text=""}, 1500);
});
}
private function remove():void
{
contactService.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:FormItem label="Phone">
<mx:TextInput id="phone" text="{contact.phone}"/>
</mx:FormItem>
<mx:FormItem label="Address">
<mx:TextInput id="address" text="{contact.address}"/>
</mx:FormItem>
<mx:FormItem label="City">
<mx:TextInput id="city" text="{contact.city}"/>
</mx:FormItem>
<mx:FormItem label="State">
<mx:TextInput id="state" text="{contact.state}"/>
</mx:FormItem>
<mx:FormItem label="Zip">
<mx:TextInput id="zip" text="{contact.zip}"/>
</mx:FormItem>
</mx:Form>
<controls:PictureInput id="picture" top="14" left="350" styleName="pictureFrame"
pictureWidth="160" pictureHeight="160"
source="{contact.pic}" />
<mx:Label id="status" left="8" bottom="50"/>
<mx:HBox left="8" bottom="8">
<mx:Button label="Save" click="save()"/>
<mx:Button label="Delete" click="remove()"/>
</mx:HBox>
</mx:Canvas>
InSync允许人们打开多个视图,所以要让每个视图知道它的某个操作是否成功。在本例中,我们会在save操作成功了显示成功的状态消息。
如果你想使用Mock的service,applicationContext.xml的定义如下:
<?xml version="1.0" encoding="utf-8"?>
<objects>
<object id="contactService" class="insync.services.ContactMockService"
</objects>
如果你想使用RemoteObjectService,只需要象下面一样修改applicationContext.xml文件,不用修改程序的任何部分,也不需要重新编译。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="utf-8"?>
<objects>
<object id="remoteObject" class="mx.rpc.remoting.mxml.RemoteObject" abstract="true">
<property name="endpoint" value="http://localhost:8400/lcds-samples/messagebroker/amf" />
<property name="showBusyCursor" value="true" />
</object>
<object id="contactRemoteObject" parent="remoteObject">
<property name="destination" value="contacts" />
</object>
<object id="contactService" class="insync.services.ContactRemoteObjectService">
<constructor-arg>
<object id="myContactRemoteObject" parent="remoteObject">
<property name="destination" value="contacts" />
</object>
</constructor-arg>
</object>
</objects>
在本版程序中,我们达到了一个重要的目的:使视图和Service实现松耦合。Spring AS框架的设计初衷就是要实现Service层与其他层的松耦合。
除此之外,你可以按照自己或团队的需求自由的实现该设计模式。比如你可以设计让你的视图不持有service或controller的引用(即使它们是接口类型)。
使用Mock service的额外好处就是你可以自己配置自己的程序,并让它运行起来,而不用担心数据库的问题(因为根本就没有数据库)。
点击此处参看程序。
安装指南
如果你已经按照第二部份的指导安装了程序,直接跳过1,3,4,5,6步。
1. 安装BlazeDS turnkey server. (http://opensource.adobe.com/blazeds)
2. 下载insyncspringas3.zip并解压缩。(http://coenraets.org/apps/insyncspringas3/srcview/springaspart3.zip)
3. 拷贝insyncspringas3/java/classes/insync目录到blazeds/tomcat/webapps/samples/WEB-INF/classes/insync下
4. 修改blazeds/tomcat/webapps/samples/WEB-INF/flex/remoting-config.xml:
<destination id="contacts">
<properties>
<source>insync.dao.ContactDAO</source>
<scope>application</scope>
</properties>
</destination>
5. 拷贝insyncspringas3/sampledb/insync目录到blazeds/sampledb/insync下
6. 修改blazeds/sampled目录下server.properties文件:
server.database.0=file:flexdemodb/flexdemodb
server.dbname.0=flexdemodb
server.database.1=file:insync/insync
server.dbname.1=insync
server.port=9002
server.silent=trueserver.trace=false
7. 启动数据库
8. 启动BlazeDS
9. 在FB中新建名为insyncspringas3的工程,不必指定Application server type
10. 拷贝spring-actionscript.swc和as3reflect.swc到工程lib目录下
11. 拷贝insyncspringas3/flex/src目录到工程的src目录下
12. 查看applicationContext.xml,确认remoteObject的endpoint与你本机的服务器设置一致
13. 运行程序
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.rpc.remoting.mxml.RemoteObject;
[Autowired]
public var contactRemoteObject:RemoteObject;
]]>
</mx:Script>
</mx:Canvas>