领先一步
VMware提供培训和认证,以加快您的进步。
了解更多自从Spring 2.0引入Spring动态语言支持以来,它一直是Groovy的一个有吸引力的集成点,而Groovy提供了一个丰富的环境来定义领域特定语言(DSL)。但是,Spring参考手册中关于Groovy集成的示例范围有限,并没有展示Spring中针对DSL集成的功能。在这篇文章中,我将展示如何使用这些功能,并以一个例子为例,从Grails发行版中使用Groovy DSL向现有的ApplicationContext添加bean定义。
Spring动态语言集成的基本功能在XML中的“lang”命名空间中公开。您可以做的最直接的事情是将Spring组件定义为Groovy bean,在一个单独的文件中或在XML中内联。Spring参考指南(http://static.springframework.org/spring/docs/2.5.x/reference/index.html)中涵盖了此功能,因此我们无需赘述,但为了完整起见,我们不妨看看一个简单的例子。
假设我们有一个Java接口
public interface Messenger {
String getMessage();
}
这是一个在Groovy中实现该接口的内联bean定义
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<lang:groovy id="messenger">
<![CDATA[
class GroovyMessenger implements spring.Messenger {
def String message;
}
]]>
</lang:groovy>
</beans>
请注意,由于Groovy为所有属性定义了公共getter和setter,因此我们不需要显式编写getMessage()方法。还要记住,Spring动态语言支持的一个特性是,内联Groovy代码也可以提取到一个单独的源文件中(使用lang:groovy元素的script-source属性)。
Spring动态语言支持的另一个特性是,脚本可以不仅仅是定义一个类。您还可以编写一个Groovy脚本,它执行一些处理,并在最后返回一个对象的实例。例如,如果我们已经有了一个名为JavaMessenger的Messenger实现
<lang:groovy id="messenger">
<![CDATA[
def messenger = new JavaMessenger("Hello World!")
messenger
]]>
</lang:groovy>
这将公开一个具有特定消息的JavaMessenger实例——一个简单的例子,但它是展示该功能的好方法。使用此技术,我们可以超越Spring中普通的bean创建模式,并在返回对象之前在脚本中进行任意多的处理。
在幕后,Spring正在创建一个groovy.util.Script的实例,其run()方法在脚本结束时返回对象。当我们开始考虑如何集成DSL时,这将非常重要。
我们需要查看的下一个功能才能进入DSL领域,是能够在将Groovy对象公开为Spring组件之前自定义Groovy对象。我相信,这个功能是在Spring 2.0发布之初的一次会议上,Rod Johnson和Guillaume Laforge在会议上提出的(它不在2.0中)。Guillaume对领域特定语言的兴趣促使他注意到,Spring处于一个有利的地位,能够在任何人有机会使用Groovy对象(或其类)之前操作和添加行为,并且由于Groovy是一种动态语言,这是一种非常强大的习惯用法。
他们提出的机制是GroovyObjectCustomizer接口,它可以在将Groovy对象公开给Spring容器客户端之前应用于该对象。该接口如下所示
public interface GroovyObjectCustomizer {
void customize(GroovyObject goo);
}
它在实例化后以及(如果它是脚本)在运行之前应用于Groovy对象。这允许我们在对象发布之前使用它的方法和属性进行操作。
要应用自定义器,我们只需在Groovy bean定义中添加对它的引用即可
<lang:groovy id="messenger" script-source="classpath:..." customizer-ref="customizer"/>
<bean id="customizer" class="..."/>
Grails有一个用于Spring组件的不错的DSL,称为BeanBuilder(有关更多详细信息,请参见此处)。它允许我们以一种非常自然和简洁的方式在Groovy中构建Spring ApplicationContext。根据Graeme Rocher的说法,在最新版本的Grails中,BeanBuilder也可以在不依赖于web框架的情况下工作——您只需要Grails Core和Groovy在您的类路径中。所以现在是看看我们能否将BeanBuilder与Spring集成的好时机(正如Spring论坛此处所指出的那样)。(实际上,我无法在没有servlet API和Spring webflow jar的情况下使用Grails 1.0-rc1使示例工作,但它可能在rc2或1.0最终版中工作。)
Groovy中领域特定语言中的表达式通常采用闭包的形式,因此使用Spring集成的脚本模式来定义闭包是很自然的。对于BeanBuilder来说,它看起来像这样
<lang:groovy id="beans">
<![CDATA[
beans = {
messenger(JavaMessenger) {
message = "Hello World!"
}
// ... more bean definitions here ...
}
]]>
</lang:groovy>
这产生一个Script对象,它本身返回一个包含bean定义的闭包(称为“beans”)。其中一个bean定义是我们的朋友messenger。理想情况下,我们希望能够获取这些bean定义并将其与当前的ApplicationContext合并。为此,我们需要使用GroovyObjectCustomizer。
public class BeanBuilderClosureCustomizer implements GroovyObjectCustomizer {
public void customize(GroovyObject goo) {
createApplicationContext(goo.run())
}
private ApplicationContext createApplicationContext(Closure value) {
BeanBuilder builder = new BeanBuilder()
builder.beans(value)
builder.createApplicationContext()
}
}
它还没有对它创建的应用程序上下文执行任何操作——只是创建它并让它消失。它也没有进行任何错误检查,但我们稍后可以添加。
public class BeanBuilderClosureCustomizer implements GroovyObjectCustomizer {
public void customize(GroovyObject goo) {
addbeanDefinitions(createApplicationContext(goo.run()))
}
private void addBeanDefinitions(ApplicationContext context) {
DefaultListableBeanFactory scriptBeanFactory = context.autowireCapableBeanFactory
for (name in scriptBeanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = scriptBeanFactory.getBeanDefinition(name)
applicationContext.autowireCapableBeanFactory.registerBeanDefinition(name, definition)
}
}
// createAppicationContext defined here....
}
还有什么能比这更简单呢?
到目前为止,我们可以加载此Spring配置
<beans>
<lang:groovy id="beans" customizer-ref="customizer">
<![CDATA[
beans = {
messenger(JavaMessenger) {
message = "Hello World!"
}
// ... more bean definitions here ...
}
]]>
</lang:groovy>
<bean id="customizer" class="BeanBuilderClosureCustomizer"/>
</beans>
然后获取messenger并使用它。在示例(参见附件)中,我们让Spring 2.5 TestContextFramework负责创建ApplicationContext并将依赖项注入测试用例(因此不需要任何依赖项查找)。
为了使我们的BeanBuilderClosureCustomizer更有用,作为最终调整,我们将修改它以使用封闭的ApplicationContext作为BeanBuilder中bean定义的父级。为此,我们只需要在自定义器中引用父级,因此我们需要实现ApplicationContextAware并使用该引用来构造BeanBuilder
public class BeanBuilderClosureCustomizer implements GroovyObjectCustomizer,
ApplicationContextAware {
def ApplicationContext applicationContext;
public void customize(GroovyObject goo) {
addbeanDefinitions(createApplicationContext(goo.run()))
}
private ApplicationContext createApplicationContext(Closure value) {
BeanBuilder builder = new BeanBuilder(applicationContext)
builder.beans(value)
builder.createApplicationContext()
}
// addBeanDefinitions defined here....
}
由于BeanBuilderClosureCustomizer是用Groovy编写的,因此我们不需要为applicationContext属性定义显式的getter和setter——它们由Groovy自动生成。
BeanBuilderClosureCustomizer现在可以使用了(可能还需要一些额外的错误检查)。Groovy真正伟大的地方在于它可以编译并作为JVM字节码打包到jar文件中。为此,我需要做的就是确保在打包我的项目时包含生成的类文件。示例通过将Groovy bean编译到Java编译器正在使用的同一目标目录中来实现此目的。
在我们的Groovy DSL中引用父上下文中的bean也很不错。Grails允许我们通过在BeanBuilder DSL中使用“ref”关键字来实现这一点,例如:
<lang:groovy id="beans" customizer-ref="customizer">
<![CDATA[
beans = {
messenger(JavaMessenger) {
message = ref("helloMessage")
}
// ... more bean definitions here ...
}
</lang:groovy>
在这里,我们从父上下文中的bean定义加载了消息。
要运行该示例,只需解压缩zip文件,或使用Eclipse将其导入现有工作区(文件->导入...->现有项目...)。如果您有Eclipse的m2插件,它应该可以开箱即用。如果没有,您可以使用m2 Eclipse插件生成Eclipse元数据(“mvn eclipse:eclipse”)。如果您没有使用Maven或Eclipse,则需要自行解决,但是您可以在pom.xml中找到顶级项目依赖项。
由于该项目在单元测试中使用JSR-250注释进行依赖注入,因此您需要使用该API。最简单的方法是使用Java 6进行运行和编译。例如,在*NIX命令行上
$ JAVA_HOME=<path-to-JDK-1.6> mvn clean test
脚注:实际上,当我在上面说我可以加载包含内联脚本的配置时,我撒谎了——由于在Spring 2.5.1中已修复的错误(请参见JIRA),它在Spring 2.5中不起作用。解决方法(如示例所示)是使用外部文件来存储脚本。