在 Spring 中使用 Groovy

1、概览

Groovy 是一种功能强大的动态 JVM 语言,具有众多特性。在 Spring 中使用 Groovy 可以大大提高应用程序的灵活性和可读性。从 Spring 4 开始 支持基于 Groovy 的配置

在本教程中,我们将了解 Groovy 与 Spring 结合使用的各种方法。首先介绍了如何使用 Spring 提供的多个选项来创建 Groovy Bean 定义。接下来,了解如何使用 Groovy 脚本加载 Application Context。最后,学习如何使用 XML 和 GroovyScriptEngine 类将 Groovy 作为脚本执行(无需编译)。

2、Maven 依赖

首先,在 pom.xml 中定义 Groovy 依赖:

<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy</artifactId>
    <version>3.0.12</version>
</dependency>

此外,还需要添加 GMavenPlus 插件来编译 Groovy 文件:

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.gmavenplus</groupId>
            <artifactId>gmavenplus-plugin</artifactId>
            <version>1.9.0</version>
            <executions>
                <execution>
                    <goals>
                        <goal>addSources</goal>
                        <goal>addTestSources</goal>
                        <goal>generateStubs</goal>
                        <goal>compile</goal>
                        <goal>generateTestStubs</goal>
                        <goal>compileTests</goal>
                        <goal>removeStubs</goal>
                        <goal>removeTestStubs</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

3、Bean 定义

传统上,开发人员通过 XML 配置来声明 Bean。这种方式后来被通过 Java 注解以编程方式定义 Bean 所取代。另一种声明 Bean 的方式是通过 Groovy 脚本。

由于我们使用的是 GMavenPlus 插件,Groovy 源文件可以与 src/main/java 源文件夹中的其他 Java 代码混合在一起。不过,最好还是将 Groovy 文件放在专用的 src/main/groovy 源文件夹中,以免后期出现混乱。

3.1、使用 Groovy Bean Builder

Groovy Bean Builder 是 Java 基于 @Configuration 注解的配置和基于 XML 的配置的强大替代品。如下,是一些基本的 Bean 定义的 Groovy 代码:

beans {
    
    // 声明一个简单的 Bean,带有一个构造函数参数
    company(Company, name: 'ABC Inc');

    // 可以使用更简单的语法声明相同的 bean:beanName(type, constructor-args)
    company String, 'ABC Inc'

    // 声明一个 employee 对象,通过其 setter 方法引用前一个 Bean
    employee(Employee) {
        firstName = 'Lakshmi'
        lastName = 'Priya'
        // 引用其他 bean 的方式有两种
        vendor = company // 或者: vendor = ref('company')
    }
    // 允许导入其他配置文件,包括 XML 和 Groovy 文件
    importBeans('classpath:ApplicationContext.xml')
    importBeans('classpath:GroovyContext.groovy')
}

如上,包含了所有 Bean 声明的 beans 结构是一个闭包,GroovyBeanDefinitionReader 将其作为 DSL 来处理。

3.2、使用注解

Groovy 类也可以是有效的 Spring Bean,在基于注解的配置中,可以用 Groovy 代替 Java:

@Configuration
class SpringGroovyConfiguration{
    
    @Bean
    List<String> fruits() {
        ['Apple', 'Orange', 'Banana', 'Grapes']
    }

    @Bean
    Map<Integer, String> rankings() {
        [1: 'Gold', 2: 'Silver', 3: 'Bronze']
    }
}

3.3、使用 XML

Groovy Bean Builder 和基于注解的配置都更加灵活。不过,我们仍然可以使用 XML 来声明在 Groovy 脚本中定义的 Bean。Groovy 是一种 动态语言,Spring 为其提供了全面的支持。首先需要在 XML 配置中使用一个特殊元素(<lang:groovy>)来表示正在定义动态语言支持的 Bean。

例如,如下 XML 配置示例,它引用了正确的 schema,所以可以使用 lang 命名空间中的标签:

<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 https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">
    <lang:groovy id="notification" script-source="file:NotificationServiceImpl.groovy" refresh-check-delay="10000" >
        <lang:property name="message" value="Hello" />
    </lang:groovy>
</beans>

这里通过 script-source 属性声明了指向 Groovy 脚本的 notification Bean。可以使用 file 前缀指定脚本的确切位置。或者,也可以使用 classpath 前缀直接从 classpath 访问资源。refresh-check-delay 属性定义了脚本的刷新间隔,当脚本内容发生变化时,可以自动刷新。

4、加载 Application Context

Spring 需要知道如何加载 Groovy Context 文件,以使应用程序可以使用 Bean。可以通过在 web.xml 中进行配置或以编程方式加载 Context 来实现这一点。

4.1、在 web.xml 中添加 Groovy 配置

Spring 4.1 使用 GroovyWebApplicationContext 增加了通过 web.xml 加载 Groovy 配置文件的支持。

默认情况下,配置将从 /WEB-INF/applicationContext.groovy 加载。这可以通过 contextConfigLocation Servlet Context Parameter 重写该位置:

<web-app>
    
    ...
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.GroovyWebApplicationContext</param-value>
    </context-param>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/applicationContext.groovy</param-value>
    </context-param>
    
    ...
</web-app>

4.2、使用 GenericGroovyApplicationContext

Spring 提供了 GenericGroovyApplicationContext 来加载 Groovy Bean 定义。此外,该 Context 可通过内联 Bean 定义闭包加载:

def context = new GenericGroovyApplicationContext()
context.reader.beans {
    department(Department) {
        name = 'Finance'
        floor = 3
    }
}
context.refresh()

或者,可以将此 Bean 定义外部化,并从 Groovy 配置文件中加载 Application Context:

GenericGroovyApplicationContext context = new GenericGroovyApplicationContext();
context.load("config/applicationContext.groovy");
context.refresh();

可以看到,加载 Groovy Context 的风格,与实例化 Java XmlWebApplicationContextClassPathXmlApplicationContext 的风格类似。

在没有额外配置的情况下,代码可以更加简洁:

ApplicationContext context = new GenericGroovyApplicationContext("config/applicationContext.groovy");
String foo = context.getBean("foo", String.class);

此外,GenericGroovyApplicationContext 还能理解 XML Bean 定义文件。这样就能与 Groovy Bean 定义文件进行无缝混合和匹配,从而增加了灵活性。

5、执行 Groovy 脚本

除了 Groovy Bean 定义,Spring 还支持在不编译的情况下执行 Groovy 脚本。这种执行方式可以是独立的 Bean,也可以是在 Bean 中调用 Groovy 脚本,使脚本成为 Bean 的可执行部分。

5.1、内联脚本

如前所述,可以使用 Spring 提供的动态语言支持,将 Groovy 源文件直接嵌入 Spring Bean 定义中。因此,可以使用 <lang:inline-script/> 元素在 Spring 配置 XML 文件中定义 Groovy 源文件。

例如,可以使用内联脚本功能创建一个 Notifier Bean:

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

    package com.baeldung.springgroovyconfig;
    import com.baeldung.springgroovyconfig.NotificationService;

    class Notifier implements NotificationService {
        String message
    }

    </lang:inline-script>
    <lang:property name="message" value="Have a nice day!" />
</lang:groovy>

5.2、使用 GroovyScriptEngine

还可以使用 GroovyScriptEngine 类来执行 Groovy 脚本。GroovyScriptEngine 由 Groovy 本身提供,使用它无需依赖 Spring。

该类支持在脚本发生变化时自动重新加载脚本。此外,它还会加载所有依赖于它的类。

执行脚本有两种方法。一种是获取一个 GroovyObject,然后通过调用 invokeMethod() 来执行脚本:

GroovyScriptEngine engine = new GroovyScriptEngine(ResourceUtils.getFile("file:src/main/resources/")
.getAbsolutePath(), this.getClass().getClassLoader());
Class<GroovyObject> joinerClass = engine.loadScriptByName("StringJoiner.groovy");
GroovyObject joiner = joinerClass.newInstance();

Object result = joiner.invokeMethod("join", new Object[]{"Mr.", "Bob"});
assertEquals("Mr.Bob", result.toString());

另一种是直接调用 Groovy 脚本。使用 Binding 类将变量传递给 Groovy 脚本:

Binding binding = new Binding();
binding.setVariable("arg1", "Mr.");
binding.setVariable("arg2", "Bob");
Object result = engine.run("StringJoinerScript.groovy", binding); 
assertEquals("Mr.Bob", result.toString());

6、总结

在本文中,我们了解了 Spring 如何为 Groovy 提供广泛支持,从而使可以使用不同的方法获得有效的 Bean 定义。此外,还了解了如何将 Groovy 脚本加载为有效的 Spring Bean。最后,学习了如何即时调用 Groovy 脚本。


参考:https://www.baeldung.com/groovy/spring-using-groovy