35. 动态语言支持

35.1 Introduction

Spring 2.0 引入了对在 Spring 中使用通过动态语言(例如 JRuby)定义的类和对象的全面支持。这种支持使您可以用受支持的动态语言编写任意数量的类,并使 Spring 容器透明地实例化,配置和依赖性注入结果对象。

当前支持的动态语言是:

  • JRuby 1.5+

  • Groovy 1.8+

  • BeanShell 2.0

Why only these languages?

之所以选择受支持的语言,是因为* a)语言在 Java 企业社区中具有很大的吸引力, b)在添加此支持时未请求其他语言,而 c)* Spring 开发人员最熟悉它们。

第 35.4 节“方案”中描述了可立即使用动态语言支持的完整示例。

35.2 第一个例子

本章的大部分内容与详细描述动态语言支持有关。在深入探讨动态语言支持的所有内容之前,让我们看一下使用动态语言定义的 bean 的快速示例。第一个 bean 的动态语言是 Groovy(该示例的基础是从 Spring 测试套件中获取的,因此,如果您希望以任何其他受支持的语言查看等效的示例,请查看源代码)。

在 Groovy bean 将要实现的Messenger接口下找到,并注意该接口是用纯 Java 定义的。注入对Messenger的引用的依赖对象将不知道基础实现是 Groovy 脚本。

package org.springframework.scripting;

public interface Messenger {

    String getMessage();

}

这是对Messenger接口具有依赖性的类的定义。

package org.springframework.scripting;

public class DefaultBookingService implements BookingService {

    private Messenger messenger;

    public void setMessenger(Messenger messenger) {
        this.messenger = messenger;
    }

    public void processBooking() {
        // use the injected Messenger object...
    }

}

这是 Groovy 中Messenger接口的实现。

// from the file 'Messenger.groovy'
package org.springframework.scripting.groovy;

// import the Messenger interface (written in Java) that is to be implemented
import org.springframework.scripting.Messenger

// define the implementation in Groovy
class GroovyMessenger implements Messenger {

    String message

}

最后,这里是将影响 Groovy 定义的Messenger实现注入到DefaultBookingService类的实例中的 bean 定义。

Note

要使用定制的动态语言标签来定义支持动态语言的 Bean,您需要在 Spring XML 配置文件的顶部具有 XML Schema 前导。您还需要将 Spring ApplicationContext实现用作 IoC 容器。支持将动态语言支持的 Bean 与纯BeanFactory实现一起使用,但是您必须 ManagementSpring 内部的管道。

有关基于架构的配置的更多信息,请参见第 41 章,基于 XML Schema 的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd">

    <!-- this is the bean definition for the Groovy-backed Messenger implementation -->
    <lang:groovy id="messenger" script-source="classpath:Messenger.groovy">
        <lang:property name="message" value="I Can Do The Frug"/>
    </lang:groovy>

    <!-- an otherwise normal bean that will be injected by the Groovy-backed Messenger -->
    <bean id="bookingService" class="x.y.DefaultBookingService">
        <property name="messenger" ref="messenger"/>
    </bean>

</beans>

bookingService bean(一个DefaultBookingService)现在可以照常使用其私有messenger成员变量,因为注入到其中的Messenger实例*是一个Messenger实例。这里没有什么特别的事情,只有普通的 Java 和普通的 Groovy。

希望上面的 XML 代码片段是不言自明的,但是如果不是,请不要过分担心。continue 阅读有关上述配置的原因的详细信息。

35.3 定义由动态语言支持的 bean

本节准确地描述了如何使用任何受支持的动态语言定义 Spring 托管 Bean。

请注意,本章并不试图解释受支持的动态语言的语法和惯用语。例如,如果您想使用 Groovy 在您的应用程序中编写某些类,则假设您已经知道 Groovy。如果您需要有关动态语言本身的更多详细信息,请参阅本章末尾的第 35.6 节“其他资源”

35.3.1 常用概念

使用动态语言支持的 bean 涉及的步骤如下:

  • 为动态语言源代码编写测试(自然)

  • 然后编写动态语言源代码本身:)

  • 在 XML 配置中使用适当的<lang:language/>元素定义动态语言支持的 bean(当然,您可以使用 Spring API 以编程方式定义此类 bean-尽管您将必须参考源代码以获取有关如何以这种类型进行此操作的指导高级配置的内容不在本章中)。请注意,这是一个迭代步骤。每个动态语言源文件至少需要一个 bean 定义(尽管同一动态语言源文件当然可以被多个 bean 定义引用)。

前两个步骤(测试和编写动态语言源文件)不在本章范围之内。请参阅语言规范和/或参考手册以了解所选择的动态语言,并 continue 开发动态语言源文件。不过,您首先将要阅读本章的其余部分,因为 Spring 的动态语言支持确实对动态语言源文件的内容做了一些(小的)假设。

<lang:language/>元素

最后一步涉及定义动态语言支持的 bean 定义,每个要配置的 bean 定义一个(这与常规 JavaBean 配置没有什么不同)。但是,可以使用<lang:language/>元素定义动态语言支持的 bean,而不是指定要由容器实例化和配置的类的完全限定的类名。

每种受支持的语言都有一个对应的<lang:language/>元素:

  • <lang:jruby/>(JRuby)

  • <lang:groovy/>(时髦)

  • <lang:bsh/>(BeanShell)

可用于配置的确切属性和子元素完全取决于定义该 bean 所用的语言(以下特定于语言的部分提供了有关此内容的完整介绍)。

Refreshable beans

Spring 的动态语言支持中最引人注目的增值之一(如果不是* the )是'refreshable bean'*功能。

可刷新 Bean 是动态语言支持的 Bean,只需少量配置,动态语言支持的 Bean 可以监视其基础源文件资源中的更改,然后在动态语言源文件更改时重新加载自身(对于开发人员编辑并将更改保存到文件系统上的文件的示例)。

这使开发人员可以将任何数量的动态语言源文件部署为应用程序的一部分,配置 Spring 容器以创建由动态语言源文件支持的 bean(使用本章中描述的机制),然后在以后随着需求的变化或其他一些外部因素也可以发挥作用,只需编辑动态语言源文件,并将所做的任何更改都反映在由更改后的动态语言源文件支持的 Bean 中。无需关闭正在运行的应用程序(或在 Web 应用程序的情况下重新部署)。如此修改的支持动态语言的 Bean 将从更改后的动态语言源文件中获取新的状态和逻辑。

Note

请注意,默认情况下此功能为* off *。

让我们看一个例子,看看开始使用可刷新 bean 是多么容易。要“打开”可刷新 bean 功能,您只需要在 bean 定义的<lang:language/>元素上精确指定一个属性即可。因此,如果我们坚持使用本章前面的the example,那么我们将在 Spring XML 配置中进行更改以实现可刷新的 bean:

<beans>

    <!-- this bean is now 'refreshable' due to the presence of the 'refresh-check-delay' attribute -->
    <lang:groovy id="messenger"
            refresh-check-delay="5000" <!-- switches refreshing on with 5 seconds between checks -->
            script-source="classpath:Messenger.groovy">
        <lang:property name="message" value="I Can Do The Frug"/>
    </lang:groovy>

    <bean id="bookingService" class="x.y.DefaultBookingService">
        <property name="messenger" ref="messenger"/>
    </bean>

</beans>

这确实是您要做的。在'messenger' bean 定义上定义的'refresh-check-delay'属性是毫秒数,此后将对基础动态语言源文件进行任何更改来刷新 bean。您可以通过为'refresh-check-delay'属性分配负值来关闭刷新行为。请记住,默认情况下,刷新行为是禁用的。如果您不希望出现刷新行为,则只需不定义该属性。

如果我们随后运行以下应用程序,则可以使用可刷新功能。请在下一段代码中原谅“通过跳跃跳转到暂停执行” *恶作剧。 System.in.read()调用仅在此处,以便在我(作者)离开并编辑基础动态语言源文件时暂停程序执行,以便在程序恢复执行时在支持动态语言的 bean 上触发刷新。

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        Messenger messenger = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger.getMessage());
        // pause execution while I go off and make changes to the source file...
        System.in.read();
        System.out.println(messenger.getMessage());
    }
}

出于此示例的目的,让我们假设必须更改对Messenger实现的getMessage()方法的所有调用,以使消息用引号引起来。以下是暂停程序执行时我(作者)对Messenger.groovy源文件所做的更改。

package org.springframework.scripting

class GroovyMessenger implements Messenger {

    private String message = "Bingo"

    public String getMessage() {
        // change the implementation to surround the message in quotes
        return "'" + this.message + "'"
    }

    public void setMessage(String message) {
        this.message = message
    }
}

程序执行时,Importing 暂停之前的输出为* I Can Do The Frug 。更改并保存对源文件的更改,并且程序恢复执行后,在支持动态语言的Messenger实现上调用getMessage()方法的结果将为'I Can Do The Frug'*(请注意包含附加引号)。

重要的是要了解,如果更改发生在'refresh-check-delay'值的窗口内,则对脚本的更改将不会触发刷新。同样重要的是要理解,在动态语言支持的 bean 上调用方法之前,实际上不会“拾取”脚本的更改。只有在支持动态语言的 Bean 上调用方法时,它才会检查其基础脚本源是否已更改。与刷新脚本有关的任何异常(例如遇到编译错误,或者发现脚本文件已被删除)都将导致致命异常传播到调用代码。

上面描述的可刷新 bean 行为不适用于使用<lang:inline-script/>元素表示法定义的动态语言源文件(请参见名为“内联动态语言源文件”的部分)。另外,它适用于可以实际检测到基础源文件的更改的 Bean。例如,通过代码检查文件系统上存在的动态语言源文件的最后修改日期。

内联动态语言源文件

动态语言支持还可以满足直接嵌入在 Spring bean 定义中的动态语言源文件的需求。更具体地说,<lang:inline-script/>元素使您可以立即在 Spring 配置文件中定义动态语言源。一个示例也许会使内联脚本功能更加清晰:

<lang:groovy id="messenger">
    <lang:inline-script>

package org.springframework.scripting.groovy;

import org.springframework.scripting.Messenger

class GroovyMessenger implements Messenger {
    String message
}

    </lang:inline-script>
    <lang:property name="message" value="I Can Do The Frug"/>
</lang:groovy>

如果将有关在 Spring 配置文件中定义动态语言源是否是一种好习惯的问题放在一边,则<lang:inline-script/>元素在某些情况下会很有用。例如,我们可能想快速将 Spring Validator实现添加到 Spring MVC Controller。使用内联源代码只是一时的工作。 (有关此类示例,请参见第 35.4.2 节“脚本验证器”。)

在下面找到一个使用inline:标记直接在 Spring XML 配置文件中定义基于 JRuby 的 bean 的源的示例。 (请注意,使用<字符表示'<'字符.在这种情况下,将内联源包围在<![CDATA[]]>区域中可能会更好.)

<lang:jruby id="messenger" script-interfaces="org.springframework.scripting.Messenger">
    <lang:inline-script>

require 'java'

include_class 'org.springframework.scripting.Messenger'

class RubyMessenger &lt; Messenger

    def setMessage(message)
        @@message = message
    end

    def getMessage
        @@message
    end

end

        </lang:inline-script>
    <lang:property name="message" value="Hello World!"/>
</lang:jruby>

在动态语言支持的 bean 的上下文中了解构造函数注入

关于 Spring 的动态语言支持,有一件非常重要的事情要注意。也就是说,(当前)无法向动态语言支持的 bean 提供构造函数参数(因此,动态语言支持的 bean 不提供构造函数注入)。为了使对构造函数和属性的特殊处理 100%清晰,以下代码和配置的混合将无效

// from the file 'Messenger.groovy'
package org.springframework.scripting.groovy;

import org.springframework.scripting.Messenger

class GroovyMessenger implements Messenger {

    GroovyMessenger() {}

    // this constructor is not available for Constructor Injection
    GroovyMessenger(String message) {
        this.message = message;
    }

    String message

    String anotherMessage

}
<lang:groovy id="badMessenger"
    script-source="classpath:Messenger.groovy">
    <!-- this next constructor argument will not be injected into the GroovyMessenger -->
    <!-- in fact, this isn't even allowed according to the schema -->
    <constructor-arg value="This will not work"/>

    <!-- only property values are injected into the dynamic-language-backed object -->
    <lang:property name="anotherMessage" value="Passed straight through to the dynamic-language-backed object"/>

</lang>

实际上,这种限制并不像它初次出现时那样重要,因为无论如何,塞特注入是绝大多数开发人员所偏爱的注入方式(让我们 continue 讨论那是否对另一天是一件好事)。

35.3.2 JRuby bean

The JRuby library dependencies

Spring 中的 JRuby 脚本支持需要以下库位于应用程序的 Classpath 中。

  • jruby.jar

从 JRuby 主页…

“ * JRuby 是 Ruby 编程语言的 100%纯 Java 实现。

与提供选择的 SpringIDEA 保持一致,Spring 的动态语言支持还支持 JRuby 语言中定义的 bean。 JRuby 语言基于非常直观的 Ruby 语言,并支持内联正则表达式,块(闭包)以及其他许多功能,这些功能确实使解决某些域问题的解决方案更加容易开发。

在 Spring 中实现 JRuby 动态语言支持的有趣之处在于发生了以下情况:Spring 创建了一个 JDK 动态代理,实现了<lang:ruby>元素的'script-interfaces'属性值中指定的所有接口(这就是为什么必须在属性值中提供至少一个接口,并在使用 JRuby 支持的 bean 时(相应地)对接口进行编程)。

让我们看一个使用基于 JRuby 的 bean 的完整示例。这是本章前面定义的Messenger接口的 JRuby 实现(为方便起见,在下面重复)。

package org.springframework.scripting;

public interface Messenger {

    String getMessage();

}
require 'java'

class RubyMessenger
    include org.springframework.scripting.Messenger

    def setMessage(message)
        @@message = message
    end

    def getMessage
        @@message
    end
end

# this last line is not essential (but see below)
RubyMessenger.new

这是定义RubyMessenger JRuby bean 实例的 Spring XML。

<lang:jruby id="messageService"
        script-interfaces="org.springframework.scripting.Messenger"
        script-source="classpath:RubyMessenger.rb">

    <lang:property name="message" value="Hello World!"/>

</lang:jruby>

注意该 JRuby 源代码的最后一行('RubyMessenger.new')。当在 Spring 的动态语言支持的上下文中使用 JRuby 时,建议您实例化并返回一个 JRuby 类的新实例,该实例将作为执行 JRuby 源代码的结果用作动态语言支持的 bean。您可以通过在源文件的最后一行上实例化 JRuby 类的新实例来实现此目的,如下所示:

require 'java'

include_class 'org.springframework.scripting.Messenger'

# class definition same as above...

# instantiate and return a new instance of the RubyMessenger class
RubyMessenger.new

如果您忘记这样做,那就不是世界的尽头了。但是,这将导致 Spring 必须(反射地)遍历 JRuby 类的类型表示形式以寻找要实例化的类。在宏伟的方案中,它是如此之快,以至于您永远不会注意到它,但是只需将诸如上面的那一行作为 JRuby 脚本的最后一行,就可以避免这种情况。如果您没有提供这样的行,或者如果 Spring 在脚本中找不到要实例化的 JRuby 类,那么在由 JRuby 解释器执行源代码后,将立即抛出一个不透明的ScriptCompilationException。可以在下面立即找到将其标识为异常的根本原因的关键文本(因此,如果您的 Spring 容器在创建动态语言支持的 bean 时抛出以下异常,并且相应的 stacktrace 中包含以下文本,则将希望您能够识别并轻松解决此问题):

org.springframework.scripting.ScriptCompilationException: Compilation of JRuby script returned ''

为了解决这个问题,只需实例化一个要公开为 JRuby 动态语言支持的 bean 的类的新实例(如上所示)。还请注意,实际上您可以在 JRuby 脚本中定义所需数量的类和对象。重要的是,整个源文件必须返回一个对象(供 Spring 配置)。

有关您可能要使用基于 JRuby 的 bean 的某些方案,请参见第 35.4 节“方案”

35.3.3 Groovy bean

The Groovy library dependencies

Spring 中的 Groovy 脚本支持需要以下库位于应用程序的 Classpath 中。

  • groovy-1.8.jar

  • asm-3.2.jar

  • antlr-2.7.7.jar

从 Groovy 主页…

“ * Groovy 是 Java 2 平台的敏捷动态语言,具有许多人们喜欢的功能,例如 Python,Ruby 和 Smalltalk,使它们可以使用类似 Java 的语法供 Java 开发人员使用.*“

如果您从上开始直接阅读了本章,那么您将已经有了看到一个例子的 Groovy 动态语言支持的 bean。让我们看另一个示例(再次使用 Spring 测试套件中的示例)。

package org.springframework.scripting;

public interface Calculator {

    int add(int x, int y);

}

这是 Groovy 中Calculator接口的实现。

// from the file 'calculator.groovy'
package org.springframework.scripting.groovy

class GroovyCalculator implements Calculator {

    int add(int x, int y) {
        x + y
    }

}
<-- from the file 'beans.xml' -->
<beans>
    <lang:groovy id="calculator" script-source="classpath:calculator.groovy"/>
</beans>

最后,这是一个用于执行上述配置的小型应用程序。

package org.springframework.scripting;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void Main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        Calculator calc = (Calculator) ctx.getBean("calculator");
        System.out.println(calc.add(2, 8));
    }
}

运行上述程序的结果输出为* 10 *(毫不奇怪)。 (令人兴奋的示例,是吧?请记住,目的只是为了说明这个概念.有关更复杂的示例,请查阅动态语言展示项目,或者在本章后面的第 35.4 节“方案”)。

重要的是,不要为每个 Groovy 源文件定义多个类。尽管这在 Groovy 中是完全合法的,但(可以说)这是一种不好的做法:为了采用一致的方法,您(在作者看来)应该尊重每个源文件一个(公共)类的标准 Java 约定。

通过回调自定义 Groovy 对象

GroovyObjectCustomizer接口是一个回调,使您可以将其他创建逻辑挂接到创建 Groovy 支持的 bean 的过程中。例如,此接口的实现可以调用任何所需的初始化方法,或设置一些默认属性值,或指定自定义MetaClass

public interface GroovyObjectCustomizer {

    void customize(GroovyObject goo);
}

Spring 框架将实例化您的 Groovy 支持的 bean 的实例,然后将创建的GroovyObject传递给指定的GroovyObjectCustomizer(如果已定义)。您可以使用提供的GroovyObject参考来做任何您想做的事情:预期大多数人都希望通过自定义MetaClass的设置来完成此回调,并且您可以在下面看到执行此操作的示例。

public final class SimpleMethodTracingCustomizer implements GroovyObjectCustomizer {

    public void customize(GroovyObject goo) {
        DelegatingMetaClass metaClass = new DelegatingMetaClass(goo.getMetaClass()) {

            public Object invokeMethod(Object object, String methodName, Object[] arguments) {
                System.out.println("Invoking '" + methodName + "'.");
                return super.invokeMethod(object, methodName, arguments);
            }
        };
        metaClass.initialize();
        goo.setMetaClass(metaClass);
    }

}

Groovy 中对元编程的完整讨论超出了 Spring 参考手册的范围。请查阅 Groovy 参考手册的相关部分,或在线进行搜索:关于该主题的文章很多。如果使用 Spring 名称空间支持,实际上使用GroovyObjectCustomizer很容易。

<!-- define the GroovyObjectCustomizer just like any other bean -->
<bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer"/>

    <!-- ... and plug it into the desired Groovy bean via the 'customizer-ref' attribute -->
    <lang:groovy id="calculator"
        script-source="classpath:org/springframework/scripting/groovy/Calculator.groovy"
        customizer-ref="tracingCustomizer"/>

如果不使用 Spring 名称空间支持,则仍可以使用GroovyObjectCustomizer功能。

<bean id="calculator" class="org.springframework.scripting.groovy.GroovyScriptFactory">
    <constructor-arg value="classpath:org/springframework/scripting/groovy/Calculator.groovy"/>
    <!-- define the GroovyObjectCustomizer (as an inner bean) -->
    <constructor-arg>
        <bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer"/>
    </constructor-arg>
</bean>

<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>

Note

从 Spring Framework 4.3.3 开始,您还可以在与 Spring 的GroovyObjectCustomizer相同的位置指定 Groovy CompilationCustomizer(例如ImportCustomizer)甚至是完整的 Groovy CompilerConfiguration对象。

35.3.4 BeanShellbean

The BeanShell library dependencies

Spring 中的 BeanShell 脚本支持需要以下库位于应用程序的 Classpath 中。

  • bsh-2.0b4.jar

在 BeanShell 主页上...

“ * BeanShell 是一个小型的,免费的,可嵌入的 Java 源代码解释器,具有 Java 编写的动态语言功能。BeanShell 可动态执行标准 Java 语法,并通过通用的脚本编写便利进行扩展,例如松散类型,命令和方法闭包,如 Perl 和 JavaScript.*“

与 Groovy 相比,BeanShell 支持的 bean 定义需要一些(小的)附加配置。在 Spring 中实现 BeanShell 动态语言支持的有趣之处在于发生了以下情况:Spring 创建了一个 JDK 动态代理,实现了<lang:bsh>元素的'script-interfaces'属性值中指定的所有接口(这就是为什么必须在属性值中提供至少一个接口,并在使用 BeanShell 支持的 bean 时(相应地)对接口进行编程)。这意味着对 BeanShell 支持的对象的每个方法调用都将通过 JDK 动态代理调用机制进行。

让我们看一个使用基于 BeanShell 的 Bean 的完整工作示例,该 Bean 实现本章前面定义的Messenger接口(为方便起见,在下面重复)。

package org.springframework.scripting;

public interface Messenger {

    String getMessage();

}

这是Messenger接口的 BeanShell“实现”(在此宽松地使用该术语)。

String message;

String getMessage() {
    return message;
}

void setMessage(String aMessage) {
    message = aMessage;
}

这是 Spring XML,它定义了上述“类”的“实例”(同样,该术语在这里非常宽松地使用)。

<lang:bsh id="messageService" script-source="classpath:BshMessenger.bsh"
    script-interfaces="org.springframework.scripting.Messenger">

    <lang:property name="message" value="Hello World!"/>
</lang:bsh>

有关您可能要使用基于 BeanShell 的 bean 的某些方案,请参见第 35.4 节“方案”

35.4 Scenarios

当然,用脚本语言定义 Spring 托管 Bean 可能会带来好处的可能场景是多种多样的。本节描述了 Spring 中对动态语言支持的两种可能的用例。

35.4.1 脚本化 Spring MVC 控制器

可以从使用动态语言支持的 bean 中受益的一组类是 Spring MVC 控制器的类。在纯 Spring MVC 应用程序中,通过 Web 应用程序的导航流程在很大程度上取决于封装在 Spring MVC 控制器中的代码。由于需要更新 Web 应用程序的导航流程和其他表示层逻辑以响应支持问题或不断变化的业务需求,因此通过编辑一个或多个动态语言源文件并查看这些变化可能很容易实现任何此类必需的更改。更改立即反映在正在运行的应用程序的状态中。

请记住,在像 Spring 这样的项目所拥护的轻量级架构模型中,您通常旨在拥有一个 true 的“薄”表示层,而应用程序的所有繁琐的业务逻辑都包含在域和服务层类中。通过将 Spring MVC 控制器开发为支持动态语言的 Bean,您可以通过简单地编辑和保存文本文件来更改表示层逻辑。对此类动态语言源文件的任何更改(取决于配置)将自动反映在由动态语言源文件支持的 Bean 中。

Note

为了实现对动态语言支持的 bean 所做的任何更改的这种自动“提取”,您必须启用“可刷新 bean”功能。有关此功能的完整说明,请参见称为“可刷新 bean”的部分

在下面找到使用 Groovy 动态语言实现的org.springframework.web.servlet.mvc.Controller的示例。

// from the file '/WEB-INF/groovy/FortuneController.groovy'
package org.springframework.showcase.fortune.web

import org.springframework.showcase.fortune.service.FortuneService
import org.springframework.showcase.fortune.domain.Fortune
import org.springframework.web.servlet.ModelAndView
import org.springframework.web.servlet.mvc.Controller

import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class FortuneController implements Controller {

    @Property FortuneService fortuneService

    ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse httpServletResponse) {
        return new ModelAndView("tell", "fortune", this.fortuneService.tellFortune())
    }

}
<lang:groovy id="fortune"
        refresh-check-delay="3000"
        script-source="/WEB-INF/groovy/FortuneController.groovy">
    <lang:property name="fortuneService" ref="fortuneService"/>
</lang:groovy>

35.4.2 脚本验证器

使用 Spring 可以从动态语言支持的 bean 提供的灵 Active 中受益的应用程序开发的另一个领域是验证领域。与常规 Java 相比,使用松散类型的动态语言(可能还支持内联正则表达式)可能更容易表达复杂的验证逻辑。

同样,将验证器开发为动态语言支持的 bean,使您可以通过简单地编辑和保存简单的文本文件来更改验证逻辑。任何此类更改(取决于配置)都会自动反映在正在运行的应用程序的执行中,而无需重新启动应用程序。

Note

请注意,为了实现对动态语言支持的 bean 的任何更改的自动“提取”,必须启用“ refreshable bean”功能。有关此功能的完整和详细说明,请参见称为“可刷新 bean”的部分

在下面找到使用 Groovy 动态语言实现的 Spring org.springframework.validation.Validator的示例。 (有关Validator界面的讨论,请参见第 9.2 节“使用 Spring 的 Validator 接口进行验证”。)

import org.springframework.validation.Validator
import org.springframework.validation.Errors
import org.springframework.beans.TestBean

class TestBeanValidator implements Validator {

    boolean supports(Class clazz) {
        return TestBean.class.isAssignableFrom(clazz)
    }

    void validate(Object bean, Errors errors) {
        if(bean.name?.trim()?.size() > 0) {
            return
        }
        errors.reject("whitespace", "Cannot be composed wholly of whitespace.")
    }

}

35.5 点滴

最后一部分包含与动态语言支持相关的一些知识。

35.5.1 AOP-为脚本 bean 提供建议

可以使用 Spring AOP 框架来建议脚本化的 bean。实际上,Spring AOP 框架并不知道建议使用的 Bean 可能是脚本 Bean,因此您可能使用或打算使用的所有 AOP 用例和功能都可以与脚本 Bean 一起使用。在为脚本 bean 提供建议时,您仅需要了解一件事(小)……您不能使用基于类的代理,而必须使用interface-based proxies

当然,您不仅限于为脚本 bean 提供建议……您还可以使用受支持的动态语言编写方面本身,并使用此类 bean 来建议其他 Spring bean。不过,这确实是对动态语言支持的高级使用。

35.5.2 Scoping

万一它不是立即显而易见的话,当然可以像其他任何 bean 一样对脚本 bean 的范围进行限定。各种<lang:language/>元素上的scope属性使您可以控制基础脚本化 bean 的范围,就像对普通 bean 一样。 (默认范围是singleton,与“常规” bean 一样。)

在下面找到一个使用scope属性定义范围为prototype的 Groovy bean 的示例。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger" script-source="classpath:Messenger.groovy" scope="prototype">
        <lang:property name="message" value="I Can Do The RoboCop"/>
    </lang:groovy>

    <bean id="bookingService" class="x.y.DefaultBookingService">
        <property name="messenger" ref="messenger"/>
    </bean>

</beans>

请参阅第 7 章,IoC 容器中的第 7.5 节“ Bean 范围”以获取有关 Spring Framework 中范围支持的完整讨论。

35.6 其他资源

在以下链接中找到有关本章中介绍的各种动态语言的更多资源的链接。