一直以来我做的Flex2项目,都是使用HTTPService通过XML报文与后端的Coldfusion CFC交互的,后端的Coldfusion我一般都是使用MACH-II框架来处理业务逻辑及返回报文的封装等,最近的一个应用开始转向使用RemoteObject直接调用Coldfusion的CFC,因为直接的CFC函数/方法调用,返回给前端的是函数执行的结果,再使用MACH-II就不是很适合了,而且一直觉得MACH-II太重了,所以就自己实现了一个调用框架。

既然是自己实现,那么首先要轻量、不要有太多配置什么的,说白了就是实现一个Flex RemoteObject调用的Coldfusion代理而已,接受Flex的调用函数请求,然后将函数调用的结果返回给Flex,另外,Flex2现在的Coldfusion调用网关的Coldfusion对象到As对象的转换已经非常智能了,所以这样的方式是一个比较合适的Flex2与Coldfusion交互的方法。

首先我们来看下该Coldfusion框架的实现:

组件名:com.eshangrao.sail.remote.RemoteFactoryBean

[cfm]
<cfcomponent name="RemoteFactoryBean" extends="com.eshangrao.sail.Base">
  <cffunction name="remoteInvoke" access="remote" output="false" returntype="any">
    <cfargument name="component_name" type="string" required="true" hint="calling component name" />
    <cfargument name="component_method" type="string" required="true" hint="calling method" />
    <cfargument name="method_arguments" type="array" required="false" hint="cailling method's arguments" />
    <cftry>
      <cfif StructKeyExists(arguments,"method_arguments")>
        <cfset keysLen=arrayLen(method_arguments) />
        <cfset callingComponent=CreateObject("component",component_name) />
        <cfset fmetas=getMetaData(callingComponent).functions />
        <cfset fmLen=arrayLen(fmetas) />
        <cfloop from="1" to="#fmLen#" index="fi">
          <cfset functionMeta=fmetas[fi] />
          <cfif functionMeta.name eq component_method>
              <cfinvoke component="#component_name#"
                method="#component_method#"
                returnVariable="callResult">
                <cfloop index = "i" from = "1" to = "#keysLen#">
                  <cftry>
                    <cfset tvar=method_arguments[i] />
                    <cfif isDefined("tvar") and tvar neq ''>
                      <cfinvokeargument name="#functionMeta.parameters&[i].name#"
                        value="#tvar#"/>
                    </cfif>
                  <cfcatch type="coldfusion.runtime.UndefinedElementException">
                    <!--- ignore null argument --->
                  </cfcatch>
                  </cftry>
                  </cfloop>
              </cfinvoke>
              <cfreturn callResult />
          </cfif>
        </cfloop>
      <cfelse>
        <cfinvoke component="#component_name#"
          method="#component_method#"
          returnVariable="callResult" />
      </cfif>
    <cfcatch type="any">
      <cfset logException(cfcatch) />
      <cfset throwException(cfcatch.type&" Exception:"&cfcatch.message) />
    </cfcatch>
    </cftry>
    <!--- not function the function on component --->
    <cfset throwException("can not find the method:"&component_method&" in component:"&component_name) />

  </cffunction>
</cfcomponent>

其中,component_name是要我们要调用的方法的组件名,component_method是我们要调用的方法,method_arguments是要调用的方法的参数。首先我们判断方法调用是否需要传参,如果不需要则直接调用组件,否则则获取组件的meta数据,根据meta key的顺序传入method_arguments中的参数值。

另外如果method_arguments中的值为null这,则需要跳过该参数传值。因为如果Flex发送给Coldfusion是一个null值的话,在<cfset tvar=method_arguments[i] />取值时会报coldfusion.runtime.UndefinedElementException错误,所以有下面的:

[cfm]
<cfcatch type="coldfusion.runtime.UndefinedElementException">
  <!--- ignore null argument --->
</cfcatch>

来捕抓错误,以实现跳过null参数不传值。

最后的Catch代码:

[cfm]
<cfcatch type="any">
    <cfset logException(cfcatch) />
    <cfset throwException(cfcatch.type&" Exception:"&cfcatch.message) />
</cfcatch>

处理组件方法调用异常,将其返回给Flex,throwException及logException方法的实现在com.eshangrao.sail.Base中:

组件名:com.eshangrao.sail.Base

[cfm]
<cfcomponent name="Base">
  <cffunction name="throwException" access="public" output="false" returntype="void" hint="throws a java Exception">
    <cfargument name="msg" type="string" required="true" hint="exception message" />
    <cfobject
      type="java"
      action="create"
      class="java.lang.Exception"
      name="exp" />

    <cfset exp.init(msg) />
    <cfthrow object=#exp# />
  </cffunction>
  <cffunction name="logInfo" access="public" output="false" returntype="void">
    <cfargument name="msg"  type="Any" required="true" hint="log object" />
    <cflog file="sail" type="information" text="#msg#" />
  </cffunction>
  <cffunction name="logException" access="public" output="false" returntype="void">
    <cfargument name="msg"  type="Any" required="true" hint="log object" />
    <cflog file="sail_exception" type="error" text="#msg#" />
  </cffunction>
</cfcomponent>

其中throwException方法中,因为Flex Coldfusion调用网关目前并不能将Coldfusion Exception自动转换到message发送给Flex,所有的Coldfusion Exception都发给Flex都是一个调用错误这样的消息,并没有错误的详细信息,因为FlexColdfusion网关是Java实现的,所以我们这里throws一个Java Exception,这样Flex Coldfusion调用网关会自己将Java Exception直接封装为message发送到Flex,这样Flex前端就可以看到详细的错误,并做相应的响应(触发fault事件)等。

现在,我们再来看Flex2中基于cairngorm调用方法,以下RemoteObject代理类中的实现,

[actionscript]
/**
 * invoke the remote_component's remote_method with args
 * @param remote_component the calling component.
 * @param remote_method the calling method.
 * @param ...args the calling method args.
 */
public function invoke(remote_component:String,remote_method:String,... args):void
{
  service.source="com.eshangrao.sail.remote.RemoteFactoryBean";
  var token:AsyncToken=service.remoteInvoke(remote_component,remote_method,args);
  token.addResponder(responder);
}

代码很简单,这里不多解释var token:AsyncToken=service.remoteInvoke(remote_component,remote_method,args)是实际调用Coldfusion方法。

下面是实际Command中的调用:

[actionscript]
public function execute(event:CairngormEvent):void
{
   var loginEvent:LoginEvent=event as LoginEvent;
   var remoteDelegate:RemoteDelegate=new RemoteDelegate(this);
   remoteDelegate.invoke("com.eshangrao.sail.system.Authentication","login",loginEvent.user,loginEvent.password);
}

这里的invoke调用是不是很简明啊?!

以上Coldfusion再结合Reactor与数据库交互,那么从前端到后端再到数据访问层都可以是面向对象的存组件的方法调用,而前端可以直接获取后端返回的CfQuery对象,非常的方便快速。

源码下载: