이 글은 ActionScript 3.0 프로젝트에서 BlazeDS와 통신하는 환경을 배경으로 한다.  Flex MXML를 배제하고 단지 Flex에 제공되는 RPC, 리모팅 관련 클래스를 사용하고자 한다. Flex MXML를 배제하는 이유는 다음과 같다.

 

Flex SDK의 Visual 컴포넌트들은 일반 솔루션 제품을 개발하는데 매우 유용하지만 어떤 경우에는 프로그램의 용량과 퍼포먼스 향상에는 도움이 안될 수 있다. 가령, Flex SDK에서 제공하는 Button과 List만 사용한다고 가정하자. 이 컴포넌트만 사용하는데도 불구하고 Flex 컴파일러는 그와 관련된 클래스까지 함께 컴파일해서 프로그램 용량을 크게 한다. 또한 Visual 컴포넌트들은 커다란 프레임워크로 만들어져 있기 때문에 Sprite만을 이용해서 만든 프로그램보다 퍼포먼스가 떨어진다.

 

물론 Flex에서 제공하는 컴포넌트를 적극적으로 다양하게 사용할 필요가 있다면 개발 효율성 및 유지보수 측면에서 Flex 프로젝트로 개발해야한다. 하지만 겨우 컴포넌트 몇개만 가져다가 사용하는 경우에는 Flex로 개발하는 것이 오히려 프로그램의 질을 떨어뜨릴 수 있는 결과를 초래할 수 있게 된다.(Flex가 나쁘다는 것이 아니라 필요에 맞게 사용해야한다는 것을 언급하는 것임을 강조한다.)  

 

Flex 환경에서 AMF3 통신

 

다음 예를 보자.

 

아래처럼 Flex 프로젝트로 만들어 Release 버전으로 컴파일 하게 되면 262kb가 나온다. 매우 단순한 프로그램인데도 불구하고 용량이 꽤 크다. 이유는 앞서 설명했다.

 

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
	<mx:RemoteObject id="ro" destination="my-dest" showBusyCursor="true">
		<mx:method
			name="getData"
			result="returnHandler(event)"
			fault="mx.controls.Alert.show(event.fault.faultString)"/>
	</mx:RemoteObject>
	<mx:Button name="load" click="load()"/>
	 <mx:Script>
	<![CDATA[
		import mx.rpc.events.ResultEvent;

		private function returnHandler(e:ResultEvent):void
		{
			trace( e.result );
		}

		private function load():void
		{
			ro.getData( 1, 2 );
		}
	]]>
	</mx:Script>
</mx:Application>

 

 

AMF3 직렬화 처리가 안된 ActionScript 3.0 프로젝트 개발

 

AMF3 통신만 원한다면 앞서 설명한 이유 때문에 굳이 Flex로 개발할 필요가 없다. ActionScript 3.0 프로젝트 환경에서도 충분히 AMF 통신이 가능하기 때문이다. 아래는 OpenAMF나 PHPAMF와 같은 서버측 프로그램을 이용할 때 ActionScript 3.0 코드이다.

 

package {
	import flash.display.Sprite;
	import flash.net.NetConnection;
	import flash.net.ObjectEncoding;
	import flash.net.Responder;
	import flash.net.registerClassAlias;

	public class AS3NetConnectionTest extends Sprite
	{
		private var nc:NetConnection;

		public function AS3NetConnectionTest()
		{
			//create a netconnection
			nc = new NetConnection();
			nc.objectEncoding = ObjectEncoding.AMF3;
			nc.connect("http://192.168.*.*/_FLEX/amf");

			//create a responder
			var responder:Responder = new Responder(result, fault);

			//make the call
			nc.call("com.jidolstar.service.MyClass.getData", responder, 2);
		}

		private function result( data:Object ):void
		{
			trace("RPC Ok");
		}

		private function fault( event:Object ):void {
			 trace("RPC Fail");
			 trace(ObjectUtil.toString(event););
		}
	}
}

 

위 코드는 Flex에 관련된 것이 없고 단순히 flash.* 패키지 기반의 순수 ActionScript 3.0 환경에서 개발한 것이다. 용량도 Release로 컴파일하면 1kb도 안된다. Flex로 개발할때와 거의 250배 이상 용량차이를 보인다.

 

 

AMF3 직렬화 처리가 된 ActionScript 3.0 프로젝트 개발

 

하지만 BlazeDS, LCDS 서버의 리모트 객체와 클라이언트 객체간에 직렬화(serialization)가 필요한 경우 위 ActionScript 코드만으로는 해결할 수 없다. 이 문제는 본인의 경우 기존 프로젝트에서 BlazeDS로 만들어진 서버측의 코드를 변경하지 않고 Flex대신 ActionScript 3.0로 개발할때 문제가 발생했다. Flex Builder에서 ActionScript 3.0 프로젝트로 하면 playerglobal.swc, flex.swc, utilites.swc만 라이브러리로 등록되어 있기 때문에 BlazeDS에서 AMF3를 통해 넘겨주는 message관련 객체나 ArrayCollection 객체등을 직렬화 처리를 할 수 없게 된다. 왜냐하면 이들 3개 swc에는 이와 관련된 클래스가 정의되어 있지 않기 때문이다.

 

BlazeDS의 message관련 객체에 대한 직렬화를 위해서는 Flex SDK에 있는 rpc.swc가 필요하다. 또 ArrayCollection 직렬화를 위해서는 framework.swc 가 필요하다. 그럼 Flex를 사용하는 것과 무엇이 다르냐라고 반문할 수 있다.  그러나 ActionScript 3.0 기반이기 때문에 framework.swc에 정의된 비주얼 컴포넌트를 전부 사용하지 않고 단지 ArrayCollection과 그와 관련된 클래스만 사용하기 때문에 이것만으로도 프로그램의 용량을 획기적으로 줄이게 된다.

 

아래에서 설명하는 대로 프로젝트 환경을 구성해보자.

 

 

1. Flex Builder 3 Professional에서 ActionScript 3.0 프로젝트를 만든다.

 

2. 프로젝트에 Flex SDK에 있는 rpc.swc, rpc_rb.swc, framework.swc, framework_rb.swc를 라이브러리로 추가한다.

 

참고로, SWC는 윈도우의 경우 C:/Program Files/Adobe/Flex Builder 3/sdks/SDK버전/frameworks/에 libs 폴더와 locale 폴더에 있다. SWC에서 _rb라고 붙은 것은 resource bundle의 약어이다. Flex는 리소스 번들을 지원해주는데 우리의 목적과는 상관없는 것이지만 컴파일을 위해서는 추가해주어야 에러가 없이 컴파일이 된다.

 

 

3. 아래처럼 컴파일러 옵션에 -locale=en_US -include-resource-bundles=collections,rpc,messaging 를 추가한다.

 

 

4. 아래 코드처럼 코딩한다. (서버측 BlazeDS 설정은 여기서 설명을 생략한다.)

 

package {
	import com.jidolstar.vo.Class1;
	import com.jidolstar.vo.Class2;

	import flash.display.Sprite;
	import flash.net.registerClassAlias;

	import mx.collections.ArrayCollection;
	import mx.collections.ArrayList;
	import mx.core.mx_internal;
	import mx.logging.targets.TraceTarget;
	import mx.messaging.ChannelSet;
	import mx.messaging.channels.AMFChannel;
	import mx.messaging.config.ConfigMap;
	import mx.messaging.config.LoaderConfig;
	import mx.messaging.events.*;
	import mx.messaging.messages.*;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.remoting.RemoteObject;
	import mx.utils.ObjectProxy;

	use namespace mx_internal;

	/**
	 * RemoteObject 이용
	 */
	public class AS3RemoteObjectTest extends Sprite
	{
		public function AS3RemoteObjectTest()
		{

			registerClassAlias("flex.messaging.messages.CommandMessage",CommandMessage);
			registerClassAlias("flex.messaging.messages.RemotingMessage",RemotingMessage);
			registerClassAlias("flex.messaging.messages.AcknowledgeMessage", AcknowledgeMessage);
			registerClassAlias("flex.messaging.messages.ErrorMessage",ErrorMessage);
			registerClassAlias("flex.messaging.io.ArrayList", ArrayList);
			registerClassAlias("flex.messaging.config.ConfigMap",ConfigMap);
			registerClassAlias("flex.messaging.io.ArrayCollection",ArrayCollection);
			registerClassAlias("flex.messaging.io.ObjectProxy",ObjectProxy);
			registerClassAlias("flex.messaging.messages.HTTPMessage",HTTPRequestMessage);
			registerClassAlias("flex.messaging.messages.SOAPMessage",SOAPMessage);
			registerClassAlias("flex.messaging.messages.AsyncMessage",AsyncMessage);
			registerClassAlias("flex.messaging.messages.MessagePerformanceInfo", MessagePerformanceInfo);
			registerClassAlias("DSA", AsyncMessageExt);
			registerClassAlias("DSC", CommandMessageExt);
			registerClassAlias("DSK", AcknowledgeMessageExt);

			registerClassAlias("com.jidolstar.service.domain.Class1", Class1 );
			registerClassAlias("com.jidolstar.service.domain.Class2", Class2);

			//Sets up some more descriptive tracing on the client
			var target:TraceTarget = new TraceTarget();
			target.level = 0;

			//This is needed so the Flex libraries have access to the root objects properties
			var swfURL:String = this.loaderInfo.url;
			LoaderConfig.mx_internal::_url = this.loaderInfo.url;
			LoaderConfig.mx_internal::_parameters = this.loaderInfo.parameters;

			//This is the channel definition
			//This tells RemoteObject where to go
			var amfChannel:AMFChannel = new AMFChannel( "my-amf", "http://192.168.0.17/amf" );
			amfChannel.requestTimeout = 3;
			amfChannel.connectTimeout = 3;
			amfChannel.addEventListener(ChannelFaultEvent.FAULT, handleChannelFault);
			amfChannel.addEventListener(ChannelEvent.CONNECT, handleChannelConnect);
			amfChannel.addEventListener(ChannelEvent.DISCONNECT, handleChannelDisconnect);

			var channelSet:ChannelSet = new ChannelSet();
			channelSet.addChannel( amfChannel );

			//Make sure you include the right RemoteObject at
			//import mx.rpc.remoting.RemoteObject;
			//not
			//import  mx.rpc.remoting.mxml.RemoteObject;
			var ro:RemoteObject = new RemoteObject;
			ro.destination = "mydest";
			ro.channelSet = channelSet;
			ro.addEventListener(ResultEvent.RESULT, onResult );
			ro.addEventListener(FaultEvent.FAULT, onFault );
			ro.getData( 1, "jidolstar" );
		}

		public function onResult(event:ResultEvent):void
		{
			trace("RPC Ok");
			trace(event.result);

		}

		public function onFault(event:FaultEvent):void
		{
			trace("RPC Fail");
        	trace(event.message);
		}

		public function handleChannelFault(e:ChannelFaultEvent):void {
		    trace("Channel Fault");
			trace(e);
		}		

		public function handleChannelConnect(e:ChannelEvent):void {
		    trace("Channel Connect");
			trace(e);
		}

		public function handleChannelDisconnect(e:ChannelEvent):void {
		    trace("Channel Disconnect");
			trace(e);
		}

	}
}

 

위 코드는 BlazeDS나 LCDS 환경에서 RPC기반인 RemoteObject로 AMF3 통신처리를 하는 ActionScript 3.0 프로젝트 예제이다.

 

registerClassAlias()는 BlazeDS에서 오는 AMF3 메시지 객체를 ActionScript 3.0으로 직렬화처리를 위한 것이다. 이 함수에 message관련 클래스와 ArrayCollection이 등록되어 있는 것을 확인하기 바란다. 이것으로서 ActionScript 3.0 프로젝트를 통해서도 충분히 ArrayCollection을 쓸 수 있게 된다.

 

registerClassAlias("com.jidolstar.service.domain.Class1", Class1 )는 서버측에 정의된 com.jidolstar.service.domain.Class1 객체를 ActionScript 3.0에 정의된 com.jidolstar.vo.Class1으로 직렬화해준다. Flex에서는 com.jidolstar.vo.Class1이 만들어진 Class1.as파일의 Class명 위에 [RemoteClass(alias="com.jidolstar.service.domain.Class1")] 를 정의함으로서 registerClassAlias()에서 한 것과 동일하게 처리할 수 있지만 ActionScript 3.0환경에서 개발하므로 [RemoteClass]는 사용하기에 적합하지 못하다.

이 프로그램을 Release모드로 컴파일하면 89kb였다. Flex로 개발시에 262kb이였던 것에 비해서 분명 크게 줄었다. 이는 Flex 비주얼 컴포넌트에 관련된 것이 컴파일에서 제외되었기 때문이다. 하지만 순수하게 NetConnection으로만 만들었던 것이 1kb였다는 것을 감안한다면 여전히 크기가 크다. 위 코드에서는 RemoteObject를 이용했는데 NetConnection을 이용해보면 어떨까? 다음 코드를 보자.

 

package {
	import com.jidolstar.vo.Class1;
	import com.jidolstar.vo.Class2;

	import flash.display.Sprite;
	import flash.net.NetConnection;
	import flash.net.ObjectEncoding;
	import flash.net.Responder;
	import flash.net.registerClassAlias;

	import mx.collections.ArrayCollection;
	import mx.messaging.messages.*;

	/**
	 * NetConnection이용
	 */
	public class AS3NetConnectionTest extends Sprite
	{
		public function AS3NetConnectionTest()
		{
			registerClassAlias("flex.messaging.messages.RemotingMessage",RemotingMessage);
			registerClassAlias("flex.messaging.messages.AcknowledgeMessage", AcknowledgeMessage);
			registerClassAlias("flex.messaging.messages.ErrorMessage",ErrorMessage);
			registerClassAlias("flex.messaging.io.ArrayCollection",ArrayCollection);

			registerClassAlias("com.jidolstar.service.domain.Class1", Class1  );
			registerClassAlias("com.jidolstar.service.domain.Class2", Class2  );

			//create a netconnection
			var nc:NetConnection = new NetConnection();
			nc.objectEncoding = ObjectEncoding.AMF3;
			nc.connect("http://192.168.0.17/amf");

			//create the arguments that will be sent to the method
			var methodArguments:Array = [ 1, "jidolstar" ];

			//create a remoting message
			var remotingMessage:RemotingMessage = new RemotingMessage();
			remotingMessage.operation = "getData";
			remotingMessage.body = methodArguments;

			//The values of these 2 values come from the services-config.xml which is located in the ColfFusion server
			remotingMessage.destination = "mydest";
			remotingMessage.headers = {DSEndpoint: "my-amf"};

			//create a responder
			var responder:Responder = new Responder(result, fault);

			//make the call
			nc.call(null, responder, remotingMessage);
		}

		private function result(e:AcknowledgeMessage):void
		{
			trace("RPC Ok");
			trace(e.body);
		}

		private function fault(e:ErrorMessage):void {
			 trace("RPC Fail");
			 trace(e.faultString);
		}
	}
}

 

위 프로그램은 RemoteObject를 사용할 때와 동일하게 동작한다. 하지만 더 저레벨로 만들었기 때문에 프로그램 용량은 44kb로 RemoteObject를 사용할때보다 1/2로 줄었다. Flex로 개발할때와 비교할때 거의 6배 차이가 난다. 그러나 여전히 순수하게 NetConnection만으로 만들었을 때와 용량차이가 나는데 그것은 메시지 관련 클래스와 ArrayCollection을 컴파일할때 그와 연관된 클래스들이 포함되어 컴파일 되기 때문이다. 그 중에서는 우리의 목적인 AMF3 직렬화와는 상관없는 resource bundle에 관련된 것도 포함되어 있다. 필요하다면 resource bundle부분을 제외한 라이브러리를 만들어 내는 것도 하나의 방법이 될 수 있겠다. 일단 6배 이상 프로그램 용량을 줄일 수 있다는 것에 만족하려고 한다. 만약 이 부분에 대해서 더욱 가볍게 만드신 분이 있다면 함께 공유했으면 한다.(제가 그것까지 하기에는 시간이 ^^;;)

 

위 프로그램은 단순한 예제이므로 필요할 때 클래스화 시켜서 만드는 것이 좋을 것이라 생각한다.

 

 

관련글

 

 

+ Recent posts