ActionScript作为一个编译型(解释+编译)语言,经常需要把一些变量存在外部文件中,如XML文档。但是经常去解析XML文件就不是一件令人愉快的事情,而且变化点不是String, Number等基本类型的话,解析完XML后还要做更多的事情,于是as-spring诞生了。
as-spring是我的一个基于Flash平台的类库,作为一个Ioc反射框架,它在平时工作中给我带来了很多便利。我以前有过几篇博客提到过它:
- 在ActionScript中使用简单的Spring框架来实现IOC
- 使用Spring来配置RemoteObject
- 发布as-spring 2.1
这次升级加入了部分新的特征,用来消除使用过程中遇到的一些不便之处。具体特征如下:
1. 简化基本类型的数据类型定义
以前定义一个Bean都是用<bean>标签来定义,如果我要定义一个String类型的Bean,需要这样来定义:
<bean class='String'>
<constructor-arg value='This is a String'/>
</bean>
现在可以用<element>标签来定义:
<element value='This is a String' type='String'/>
2. 增加了数组定义
以前定义数组没有好的方法,特别是当数组的元素不是基本类型时。现在可以用<list>标签来定义:
<list id='arr'>
<element value='true'/>
<bean>
</bean>
<list>
</list>
</list>
3. 增加了Hash Object的定义
这个功能其实在以前就可以很方便的实现,只是现在加入了一个更符合思维的定义方式:
<map>
<key name='apple' value='[iPhone,iPad]'>
<key name='google'>
<value>android</value>
</key>
<key name='ms'>
<list>
<element value='XP'/>
<element value='Vista'/>
<element value='Win7'/>
</list>
</key>
</map>
本来打算加入对flash.utils.Dictionary的定义的,最后还是放弃了,主要是因为暂时没有遇到迫切需要Dictionary的应用场景,如果以后遇到了,可以考虑加入这个功能。
Merapi是一个用于Java和AIR之间通信的桥接库。通过它可以极大的扩展AIR在本地的执行能力,当然前提是客户端不仅装有AIR运行时虚拟机,还需要安装有Java虚拟机。Merapi通过Socket来通信,使用Adobe的AMF作为数据传输格式。下面看看如何在Flash Builder中创建一个基于Adobe AIR的桌面软件。
在Java和ActionScript两端都存在一名为Bridge的类,它是一个Singleton,用来注册消息类型和发送消息。在这个例子中,AIR向Java端发送一个名为”flashEvent”的消息,消息内容是一个Dictionary实例;并且监听名为”javaEvent”的消息。
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" creationComplete="initApp()">
<mx:Script>
<![CDATA[
import demo.JavaMessageHandler;
import demo.SerializeMessageHandler;
import merapi.Bridge;
import merapi.messages.Message;
import merapi.error.MerapiErrorMessage;
private function initApp():void{
//register a error handler
new SerializeMessageHandler();
//send a message
var message:Message=new Message();
message.type="flashEvent";
var data:Dictionary=new Dictionary();
data["name"]="colorhook";
message.data=data;
message.send();
//register a message handler
Bridge.getInstance().registerMessageHandler("javaEvent",
new JavaMessageHandler());
}
]]>
</mx:Script>
</mx:WindowedApplication>
CoreErrorHandler继承自EventDispatcher,实现了IMessageHandler接口,它在构造函数中就集成了错误处理。所以不需要显示的在Bridge中注册,而是构造一个实例就行了。SerializeMessageHandler继承自CoreErrorHandler,用来处理全局错误。
package demo{
import merapi.handlers.mxml.CoreErrorHandler;
import mx.rpc.events.ResultEvent;
public class SerializeMessageHandler extends CoreErrorHandler{
public function SerializeMessageHandler(type:String=null){
super(type);
this.addEventListener(ResultEvent.RESULT,onMessageResult);
}
private function onMessageResult(event:ResultEvent):void{
trace("SerializeMessageHandler:",event);
}
}
}
JavaMessageHandler实现了接口IMessageHandler,并在handleMessage方法中输出从Java中返回的数据。
package demo{
import merapi.handlers.IMessageHandler;
import merapi.messages.IMessage;
public class JavaMessageHandler implements IMessageHandler{
public function handleMessage(message:IMessage):void{
trace(message.type);
trace(message.data);
}
}
}
编译运行会发现控制台输出SerializeMessageHandler中的trace信息,因为目前还没有开启Java端的ServerSocket。所以下面来启动一个Java程序。首先调用Bridge.open()来启动ServerSocket。然后监听名为”flashEvent”的消息。
package demo;
import org.apache.log4j.Logger;
import merapi.Bridge;
public class Application {
protected Logger logger=Logger.getLogger(Application.class);
public Application(){
logger.info("Application start");
//open Bridge
Bridge.open();
//register a message handler
Bridge.getInstance().registerMessageHandler("flashEvent",
new AIRMessageHandler());
}
public static void main(String[] args){
new Application();
}
}
在消息处理器AIRMessageHandler中,输出来自Flash的数据,AMF将ActionScript中的Dictionary映射为Java中的Map。接受到数据之后,我们再从Java中发出一个Message,该Message携带了一个List类型的数据,在Flash中,它会被映射成Array类型。
package demo;
import java.util.*;
import org.apache.log4j.Logger;
import merapi.handlers.IMessageHandler;
import merapi.messages.IMessage;
import merapi.messages.Message;
public class AIRMessageHandler implements IMessageHandler {
protected Logger logger=Logger.getLogger(AIRMessageHandler.class);
@Override
public void handleMessage(IMessage message) {
//handle message
logger.info(message.getType());
Map<String,String> map=(Map<String, String>)message.getData();
logger.info(map.get("name"));
//send a new message
Message newMessage=new Message();
newMessage.setType("javaEvent");
List<String> list=new ArrayList<String>();
list.add("Flash Player");
list.add("AIR");
newMessage.setData(list);
newMessage.send();
}
}
Merapi在Java端用到了Spring, log4j和Adobe AMF相关包, 所以运行时要保证项目包含了这些jar包。可以从Merapi的Google Code中得到这些包。
AIR2的发布,让我们在AIR和Java通信方面有了新的选择: flerry。
flerry使用了AIR2的新特性NativeProcess,它直接创建了一个进程了执行Java程序。从其作者的blog上可以了解更多信息: http://www.riaspace.net/2010/01/flerry-flex-java-bridge-for-adobe-air-2-0/
此版本已更新,具体请看:发布as-spring 2.4
由于Christophe Herreman 的actionscript-spring框架已经十分的成熟和完善,而自己也有个简单的库叫actionscript-spring,于是更名为as-spring以示区别,现在升级到2.1版本,相对于2.0增加的功能有:
1.支持import多配置文件。
<import resource="spring-config2.xml"/>
<import resource="spring-config3.xml"/>
2.支持属性内联Bean。
<bean id="dp" class="Object">
<property name="prop">
<bean class="Object">
</bean>
</property>
</bean>
3.支持参数内联Bean。
<bean id="source" class="Array">
<constructor-arg>
<bean class="flash.display.Sprite"/>
</constructor-arg>
<constructor-arg>
<bean class="flash.media.Sound"/>
</constructor-arg>
</bean>
<bean id="loader" class="flash.net.URLLoader">
<method name="load">
<method-arg>
<bean class="flash.net.URLRequest">
<property name="url" value="http://www.colorhook.com"/>
</bean>
</method-arg>
</method>
</bean>
在Flex中使用RemoteObject可以很容易地对Java对象进行RPC调用,但是其配置较为复杂。使用AMFPHP对PHP进行RPC调用只需要指定RemoteObject的destination和endpoint属性即可,而在BlazeDS环境下,需要创建ChannelSet和Channel对象。
通常的做法是使用一个xml文件来配置一个Java destination,Flex在编译的时候会根据配置信息来创建一个完整的RemoteObject对象。但是如果需要更改该RemoteObject对象的destination或者ChannelSet等属性,则需要重新编译,这样的话很是麻烦。
Spring的IOC容器可以很好解决这个问题,它把对象的创建方式声明在外部,然后由容器来动态生成。
使用我的Spring框架来实现这个想法。
1.创建远程的Java对象,编译成class文件放在文件夹WEB-INF\classes\demo下面。
package demo;
public class HelloBlazeDS{
public HelloBlazeDS(){
}
public String sayHello(String info){
return "[BlazeDS return] : "+info;
}
}
2.配置BalzeDS。
打开文件WEB-INF\flex\remoting-config.xml,向其中添加一个destination定义。
<destination id="HelloBlazeDS">
<properties>
<source>demo.HelloBlazeDS</source>
</properties>
</destination>
3.创建Spring的配置文件spring_config.xml。
<?xml version="1.0" encoding="utf-8"?>
<spring-config>
<beans>
<bean id="remoteBean" class="mx.rpc.remoting.mxml.RemoteObject">
<property name="destination" value="HelloBlazeDS"/>
<property name="channelSet" ref="channelSet"/>
</bean>
<bean id="channelSet" class="mx.messaging.ChannelSet">
<method name="addChannel">
<method-arg ref="channel"/>
</method>
</bean>
<bean id="channel" class="mx.messaging.channels.AMFChannel">
<property name="id" value="my-amf"/>
<property name="uri" value="http://localhost:8080/blazeds/messagebroker/amf"/>
</bean>
</beans>
</spring-config>
该配置文件声明了一个Bean名字叫remoteBean,它是一个RemoteObject对象,这个Bean还引用了channelSet和channel这两个Bean。
4.在Flex中使用Spring,下面是代码片段。
//导入加载配置文件的类ContextLoader
import com.colorhook.spring.context.ContextLoader;
private var contextLoader:ContextLoader;
private var remoteService:RemoteObject;
//初始化时加载配置文件
private function init():void{
contextLoader=new ContextLoader();
contextLoader.addEventListener("complete",onContextLoaderComplete);
}
//加载完成之后创建RemoteObject,并调用远程方法sayHello
private function onContextLoaderComplete(event:Event):void{
contextLoader.removeEventListener("complete",onContextLoaderComplete);
remoteService=contextLoader.contextInfo.getBean("remoteBean");
remoteService.sayHello.addEventListener(ResultEvent.RESULT,onSayHelloResult);
remoteService.sayHello("Spring and BlazeDS");
}
//输出返回结果
private function onSayHelloResult(event:ResultEvent):void{
Alert.show(String(event.result));
}
比利时的Christophe Herreman是Spring ActionScript框架(以前的pranaframework)的创始人和主要开发成员之一,Spring ActionScript的当前版本是0.7。
当前版本的Spring ActionScript已经实现了IOC,反射编程框架,集成了Cairngorm和PureMVC框架。接下来会开发一个MVCS( Model-View-Controller-Service)的架构,并且加入AOP(Aspect Oriented Programming)面向切面编程。(比我的Spring框架不知道要强多少倍!!)
使用pranaframework框架的一个例子(与使用Spring ActionScript类似,只是包名改了):
1.定义XML配置文件.
prana_config.xml
<?xml version="1.0" encoding="utf-8"?>
<objects>
<object id="sound" class="flash.media.Sound"/>
<object id="request" class="flash.net.URLRequest">
<constructor-arg type="String" value="testSound.mp3"/>
</object>
</objects>
2.加载配置文件,生成对象.
ActionScript时间轴代码
import org.pranaframework.context.support.XMLApplicationContext;
var context:XMLApplicationContext=new XMLApplicationContext("prana_config.xml");
context.addEventListener("complete",onContextComplete);
context.load();
function onContextComplete(e:Event):void{
var sound=context.getObject("sound")
sound.load(context.getObject("request"))
sound.play()
}
此版本已更新,具体请看:发布as-spring 2.4
Flash跨平台的实现和Java一样,都是通过虚拟机来实现的,Flash Player充当了适配器(Adapter)的作用。swf文件是编译过的二进制文件,类似与Java的class文件。一般来说,如果改变swf文件,需要修改源文件然后重新编译,这样或多或少会带来麻烦。IOC(Inversion Of Control)是用来分离关注的一种模式,大名鼎鼎的Java Spring框架就是以IOC容器来实现DI(Dependency Injection)。Java Spring的IOC容器是XML驱动的,意味着可以修改XML文件来更改程序的行为。把这种方式移植到Flash平台,就成了Flash的Spring了。
1.加载参数
该功能在新版本中被移除了,有新的方式可以替代。
在应用中可能会有随时更改的参数,或者这个参数还没有得到确定。如果把参数写进XML文件中,即使以后参数发生更改,也不需要重新编译程序。
为这个过程建立一个统一的接口,以后的相同的需求就可以重复使用。
这个Spring配置文件定义的7个参数,分别对应基本类型Number,int,uint,String,Boolean,Array,Date
spring_config.xml
<?xml version="1.0" encoding="utf-8"?>
<spring-config>
<params>
<param name="p_Number" type="Number" value="3.14159265358979323"/>
<param name="p_int" type="int"><value>-1</value></param>
<param name="p_uint" type="uint" value="1"/>
<param name="p_String" type="String" value="string"/>
<param name="p_Boolean" type="Boolean" value="false"/>
<param name="p_Array" value="[1,2,3,4,5]"/>
<param name="p_Date" type="Date" value=""/>
</params>
</spring-config>
SpingLoadParameterExample.as
package{
/**
* @author colorhook
* @copyright http://www.colorhook.com
*/
import com.colorhook.spring.context.ContextLoader;
import com.colorhook.spring.context.ContextInfo;
import flash.display.Sprite;
import flash.events.Event;
public class SpingLoadParameterExample extends Sprite{
private var contextLoader:ContextLoader;
public function SpingLoadParameterExample(){
contextLoader=new ContextLoader();
contextLoader.addEventListener(Event.COMPLETE,onContextLoaderComplete);
contextLoader.load("spring_config.xml");
}
private function onContextLoaderComplete(e:Event):void{
print("p_Number");
print("p_int");
print("p_uint");
print("p_Boolean");
print("p_String");
print("p_Array");
print("p_Date");
}
private function print(name:String):void{
var contextInfo:ContextInfo=contextLoader.contextInfo;
var param:*=contextInfo.getParameter(name);
trace("[Parameter] ",name," : ",param);
}
}
}
2.从SWF文件中加载Class
通过加载一个SWF文件,可以使用其中包含的类。你可能为了减小单个文件的尺寸把文件分割成一块一块,类定义也是一样的,把一个类库编译成一个swf文件,然后在需要的时候加载它,这有点想RSL,但是与RSL还是有区别的,Flash Player可以缓存RSL,而swf被浏览器缓存。
这个Spring配置文件定义的2个library和2个Class,2个lib中都有名为MyCircle的类,circleInNewDomain会被加载到独立的应用域。
spring_config.xml
<?xml version="1.0" encoding="utf-8"?>
<spring-config>
<libs>
<lib name="circleInSameDomain" path="CircleInSameDomain.swf"/>
<lib name="circleInNewDomain" path="CircleInNewDomain.swf" same-domain="false"/>
</libs>
<classes>
<class name="CircleInNewDomain" class="MyCircle" lib="circleInNewDomain" />
<class name="CircleInSameDomain" class="MyCircle" lib="circleInSameDomain" />
</classes>
</spring-config>
SpringLoadClassExample.as
package {
/**
* @author colorhook
* @copyright http://www.colorhook.com
*/
import com.colorhook.spring.context.ContextLoader;
import com.colorhook.spring.context.ContextInfo;
import flash.display.Sprite;
import flash.events.Event;
import flash.display.MovieClip;
public class SpringLoadClassExample extends Sprite {
private var contextLoader:ContextLoader;
public function SpringLoadClassExample() {
contextLoader=new ContextLoader();
contextLoader.addEventListener(Event.COMPLETE,onContextLoaderComplete);
contextLoader.load("spring_config.xml");
}
private var ClassInSameDomain:Class;
private var ClassInNewDomain:Class;
private function onContextLoaderComplete(e:Event):void {
var contextInfo:ContextInfo=contextLoader.contextInfo;
ClassInSameDomain=contextInfo.getClass("CircleInSameDomain");
ClassInNewDomain=contextInfo.getClass("CircleInNewDomain");
var instanceInSameDomain:MovieClip=new ClassInSameDomain() as MovieClip;
var instanceInNewDomain:MovieClip=new ClassInNewDomain() as MovieClip;
instanceInNewDomain.x=200;
addChild(instanceInSameDomain);
addChild(instanceInNewDomain);
trace("ClassInSameDomain:",ClassInSameDomain);
trace("ClassInNewDomain:",ClassInNewDomain);
}
}
}
3.通过反射生成对象
使用XML来定义一个对象,然后在特别的某个时刻来生成他,然后注入到你的应用程序中,这可以降低程序的耦合性,提高软件的可维护性。
通过XML定义,可以创建类的实例,也可以用过工厂方法得到一个对象。
这个Spring配置文件定义的四个Bean。personBean是一个单例,类型是Object。requestBean是最普通的Bean。randomNum是一个工厂Bean,sprite也是普通的Bean,它定义了创建后执行的动作。
spring_config.xml
<?xml version="1.0" encoding="utf-8"?>
<spring-config>
<beans>
<bean id="personBean" class="Object" singleton="true">
<property name="name" value="John"/>
<property name="sex"><value>male</value></property>
</bean>
<bean id="requestBean" class="flash.net.URLRequest">
<constructor-arg type="String" value="http://www.colorhook.com"/>
<property name="method" value="GET"/>
</bean>
<bean id="randomNum" class="Math" factory-method="random">
</bean>
<bean id="sprite" class="flash.display.Sprite">
<property name="graphics">
<method name="beginFill">
<method-arg value="0xFF0000"/>
</method>
<method name="drawRect">
<method-arg value="0"/>
<method-arg value="0"/>
<method-arg value="100"/>
<method-arg value="100"/>
</method>
</property>
<property name="x" value="200"/>
<property name="y" value="100"/>
</bean>
</beans>
</spring-config>
SpringLoadBeanExample.as
package {
/**
* @author colorhook
* @copyright http://www.colorhook.com
*/
import com.colorhook.spring.context.ContextLoader;
import com.colorhook.spring.context.ContextInfo;
import flash.display.Sprite;
import flash.events.Event;
import flash.net.URLRequest;
public class SpringLoadBeanExample extends Sprite {
private var contextLoader:ContextLoader;
private var contextInfo:ContextInfo;
public function SpringLoadBeanExample() {
contextLoader=new ContextLoader();
contextLoader.addEventListener(Event.COMPLETE,onContextLoaderComplete);
contextLoader.load("spring_config.xml");
}
private function onContextLoaderComplete(e:Event):void {
contextInfo=contextLoader.contextInfo;
testRefBean();
trace("--------------------------------");
testSingletonBean();
trace("--------------------------------");
testFactoryBean();
trace("--------------------------------");
testMethodBean();
}
private function testRefBean():void {
var requestBean:URLRequest=contextInfo.getBean("requestBean");
trace(requestBean);
trace(requestBean.url);
trace(requestBean.method);
}
private function testSingletonBean():void {
var personBean:Object=contextInfo.getBean("personBean");
printObject(personBean);
personBean.blog="http://www.colorhook.com/blog";
personBean=contextInfo.getBean("personBean");
printObject(personBean);
}
private function testFactoryBean():void {
var bean:*=contextInfo.getBean("randomNum");
trace(bean);
bean=contextInfo.getBean("randomNum");
trace(bean);
}
private function testMethodBean():void{
var sprite:Sprite=contextInfo.getBean("sprite");
addChild(sprite)
}
private function printObject(object:*):void {
for (var i in object) {
trace(i," : ",object[i]);
}
}
}
}
可以从下面的地址下载到框架的源文件,示例的源文件和帮助文档。
http://www.colorhook.com/Spring_ActionScript3_V2.zip
源码和文档已托管在google code上:http://code.google.com/p/as-spring