Adobe公司的ColdFusion已经在开发社区广受欢迎。它这种突飞猛进似的发展是因为有这样一种反馈循环:更多强大的工具和框架促使应用程序开发人员变得更加优秀,而更加优秀的开发人员又能开发出更多强大的工具和框架。这种不断成功中的一个核心部件就是ColdSpring框架。我将在本文里向您介绍ColdSpring,解释它是什么,为什么您应该使用它,并提供一些体现它的功能的例子。
什么是ColdSpring?
ColdSpring是一个由Chris Scott和Dave Ross(在其他社区的领导者的帮助下)为ColdFusion开发的一个依赖注入(Dependency Injection)框架。ColdSpring基本上以Java的Spring框架为基础,被用来管理应用程序域模型里的依赖性。这种能力也被叫做控制反转(Inversion of Control),但是这种说法对于我来说似乎还不够直观,所以我坚持使用依赖注入这种叫法。
在ColdFusion里,对象模型是使用ColdFusion组件(CFC)实现的。所以在实际使用中,ColdSpring提供的方式避免了辛苦地手动管理CFC之间的关系。当然ColdSpring的功能不止管理依赖性,但这是它的主要职责。
为什么要使用ColdSpring?
优秀的面向对象(OO)设计做法要求您应该更多地使用合成而不是继承。按照这种方式来开发通常意味着您的CFC常常会带有到一个(或者多个)其他CFC的指针作为实例变量。这为您的设计提供了很大的灵活性,但是它的一个不足之处是您不得不管理所有这些依赖性。
让我们想象一个带有两层功能的域模型:一个服务层和一个数据库抽象层。例如,UserService可能会依靠一个UserDAO和一个UserGateway来与数据库进行交互操作。而反过来,您的UserDAO和UserGateway这两个CFC可以依靠配置CFC(Configuration CFC)提供关于数据源的名称、秘密等信息,而实用工具CFC(Utility CFC)可以提供一些一般的功能,例如调整结构中所有的值。
在本文中,您不仅仅可以管理这些组件之间的依赖性,它们还必须按照特定的结构被创建。实用工具CFC和配置CFC都必须先被创建,然后供UserDAO和UserGateway使用。最后,UserDAO和UserGateway都必须在被提供给UserService之前被创建。
依赖注入能力
现在让我们来看看ColdSpring如何能够帮助进行管理CFC的依赖性。您可以以多种方式定义依赖性,但是最常见的方法是使用一个XML文件。用代码初始化ColdSpring非常容易。(列表A)
列表A
<cfsetserviceDefinitionLocation = expandPath('services.xml' ) />
<cfsetcoldSpring = createObject( 'component', 'coldspring.beans.DefaultXmlBeanFactory').init() />
<cfsetcoldSpring.loadBeansFromXmlFile(serviceDefinitionLocation) />
一旦完成了这一步,ColdSpring就可以供使用了。要让ColdSpring创建CFC,您只需要调用getBean()方法,并传递所需要的组件的名称就行了,就像下面这样:(列表B)
列表B
<cfset test = coldSpring.getBean('test')/>
我准备向您展示一个示例ColdSpring XML,然后一段一段地浏览,讨论ColdSpring会相应地做什么(列表C)。
<?xmlversion="1.0"encoding="UTF-8"?>
<beans>
<beanid="user">
<propertyname="utility">
<refbean="utility"/>
</property>
</bean>
<beanid="utility"/>
<beanid="testFactory"/>
<beanid="test"factory-bean="testFactory"factory-method="getInstance">
<constructor-argname="parameters">
<map>
<entrykey="user">
<refbean="user"/>
</entry>
</map>
</constructor-arg>
</bean>
</beans>
上面的XML用到了多种不同的特性。首先要注意ColdSpring将多种组件识别为“beans”,这主要是源自于Spring的遗留物。有的时候我希望这个名字更加贴切一点,因为它们可能会使用者带来“bean”是什么的困惑。但是,如果您只把它们当成CFC,这就不会有任何问题。
第一个定义的bean是一个叫做User的CFC。它有一个叫做Utility的属性。如果接着这个定义往后看,您会看到第二个定义的是实用工具CFC。所以我们马上得出了结论,ColdSpring会:
- 启动一个Utility实例
- 启动一个User实例
- 由于我们告诉ColdSpring说User CFC要依赖Utility,所以ColdSpring会自动地寻找一个匹配被注入的bean的名字的setter方法(在这里是setUtility())。
- 最后,ColdSpring通过对User调用setUtility()方法把Utility注入到User里,同时把Utility实例作为一个自变量传递。这就叫做setter注入。
所以ColdSpring不仅会为您解决依赖性的问题,它甚至还能够自动地解决它们的排序问题。但是,从ColdSpring获得新的CFC还有其他的方式,见下面第三和第四个bean的定义。
上面列表C里的第三个定义定义了一个叫做TestFactory的CFC。正如您猜到的,这是一个工厂(Factory),也就是说,它的职责是创建其他的CFC。您可能会疑惑“为什么我们需要工厂?难道ColdSpring不已经是一个工厂了?”
是的,ColdSpring是一个工厂,但只是一个简单的工厂。通过允许我们从ColdSpring调用自定义的工厂,我们可以对已创建的CFC具有更大的控制权,让其具有更强的功能。只用想象一个简单的例子:我们想要一个ContactManager CFC,以便让服务器与系统管理员进行通信。在上班时间里,它可以创建一个处理电子邮件的CFC,但是在下班时间里,它会创建一个发送传呼信息或者文本短消息的CFC。向我们的工厂里添加一些额外的逻辑以便根据不同的情况实例化不同的CFC很容易。
正如您可以从最后一个bean的定义里看到的,我在使用TestFactory CFC,并让ColdSpring对它调用getInstance()方法。我还将一些数据直接传递给了工厂方法:一个带有User键的结构命名参数和一个我们先前创建的User CFC的值。
您可以使用这种叫做构造函数注入(constructor injection)的方式把依赖性直接注入到任何bean里,而不需要用先前讲到的setter注入方式。这两种方式都有各自的优点和缺点,但是setter注入的一个优势是您可以避免循环依赖性的问题。如果CFC1需要将CFC2作为构造函数自变量,但是CFC2也需要CFC1作为构造函数自变量,那么这就会出现循环依赖性的问题。
回到这个例子上,我们现在知道了ColdSpring将会根据我们定义的bean作出什么样的响应。现在我们可以开始使用它了。(列表D)
列表D
<cfset test = coldSpring.getBean('test') />
<cfoutput>date: #test.getDate()#</cfoutput>
总而言之,作为对调用getBean的响应,ColdSpring会:
- 启动一个Utility实例
- 启动一个User实例,把Utility注入到user.setUtility()里
- 启动一个TestFactory实例
- 调用testFactory.getInstance()并将一个数据结构作为自变量传递,后者会返回一个叫做Test的CFC。
好了,那现在又该怎么办呢?
上面所有这些都是人为的例子,为的是说明如何使用ColdSpring的一些功能。但是您可能会说,“我可以通过手动创建对象的方式自己做到这一切!”您当然可以。如果您不处理大量的CFC,像ColdSpring这样的框架的好处不会真正完全显现出来。
在上面这些例子里,管理几个CFC还不算很复杂的事情。管理200个CFC之间的依赖性就完全是另外一回事了。如果您以错误的顺序创建了CFC,那么它就会崩溃。如果忘记正确地传递一个CFC,那么它就会崩溃。ColdSpring可以为您完成这些工作。最后,ColdSpring的XML会为CFC依赖性制作一份优秀的图谱。您可以在XML文件的这个地方就看到所有的依赖性,而不是浏览众多的组件来试图弄清楚它们的依赖性。
大多数用于ColdFusion的流行MVC框架都通过插件或者适配器直接支持ColdSpring。很多甚至支持“自动连接(autowiring)”,这样当您在XML里定义了bean之后,插件会通过查找匹配bean名字的setter尝试向框架组件提供由ColdSpring生成的bean。Model-Glue、Mach-II和Fusebox都具备这种功能。
面向剖面的能力
除了依赖注入的能力,ColdSpring还向ColdFusion的用户引入了面向剖面编程(Aspect-Oriented Programming,AOP)的概念。AOP是解决特定类型问题的一种强大方式。AOP的一个常见用处是日志记录。您可能希望在某些方法被执行的时候写一个日志文件。在标准的面向对象编程(OO)方式里,您必须向用于处理日志记录的方法加入代码。
AOP改变了这种方式。您不需要在所有的地方都添加日志记录代码,而只用一次定义日志记录代码,然后AOP截取整个模型里的方法调用,并在方法调用的时候运行日志记录代码。
在实际操作中,ColdSpring会创建一个包装程序CFC(wrapper CFC),对原始的CFC进行扩展,并在调用这个CFC的时候把包装程序组件返回给您。您的代码甚至不知道它在处理包装程序而不是原始的组件;ColdSpring会透明地管理这些。它为所有原始CFC的方法都创建代理,但是允许您在代码执行之前和之后添加额外的代码(比如日志记录代码)。
Barney Boisvert有一个关于AOP代码的好例子,它会自动地在cftransaction块里装入您希望加入的任何方法调用。这使得您可以自动地添加或者删除对方法调用的数据库事务管理!您可以在他的博客里找到这段代码。
AOP能力的最后一个例子是RemoteFactoryBean。您可以通过使用AOP来接受任何模型组件,并为它生成一个远程的Façade组件。这可以让您将某些或者全部的方法向远程源开放,例如Web服务、AJAX或者Flash Remoting调用等。您甚至可以使用ColdSpring通过FlashUtilityService自动地将CFC数据转化成为ActionScript对象。
动手试试
我只能向您讲解ColdSpring主要特性的一些皮毛,但是我希望您能够知道它将很多强大的功能带到了您的面前。它是用于ColdFusion的依赖注入和AOP的实现,经过测试,性能稳定。您可以下载ColdSpring的示例代码,并阅读完整的文档。网上还有很多到CVS资源库的链接和关于这个话题的专用邮件列表。您自己动手试试就会体会到它的作用!