Language Support

1. Kotlin

Kotlin是针对 JVM(和其他平台)的静态类型语言,它允许编写简洁明了的代码,同时为使用 Java 编写的现有库提供很好的interoperability

Spring 框架为 Kotlin 提供了一流的支持,使开发人员几乎可以将 Spring 框架当作原生 Kotlin 框架来编写 Kotlin 应用程序。

了解 Spring 和 Kotlin 的最简单方法是遵循本综合教程。如果需要支持,可以随时加入Kotlin Slack的#springChannels,或在Stackoverflow上提问带有springkotlin作为标签的问题。

1.1. Requirements

Spring 框架支持 Kotlin 1.1,并要求kotlin-stdlib(或其其中一个变体,例如 Kotlin 1.1 的kotlin-stdlib-jre8或 Kotlin 1.2 的kotlin-stdlib-jdk8)和kotlin-reflect出现在 Classpath 上。如果您在start.spring.io上引导 Kotlin 项目,则默认情况下会提供它们。

1.2. Extensions

Kotlin extensions提供了使用其他功能扩展现有类的功能。 Spring Framework Kotlin API 使用这些扩展为现有的 Spring API 添加了新的 Kotlin 特定的便利。

Spring Framework KDoc API列出并记录了所有可用的 Kotlin 扩展和 DSL。

Note

请记住,必须导入 Kotlin 扩展才能使用。例如,这意味着仅当导入org.springframework.context.support.registerBeanGenericApplicationContext.registerBean Kotlinextensions 才可用。就是说,类似于静态导入,在大多数情况下,IDE 应该自动建议导入。

例如,Kotlin 修饰类型参数为 JVM 泛型类型擦除提供了一种解决方法,而 Spring Framework 提供了一些扩展以利用此功能。这允许使用更好的 Kotlin API RestTemplate,Spring WebFlux 的新WebClient以及各种其他 API。

Note

其他库,例如 Reactor 和 Spring Data,也为其 API 提供了 Kotlin 扩展,因此总体上提供了更好的 Kotlin 开发经验。

要检索 Java 中的User对象的列表,通常需要编写以下内容:

Flux<User> users  = client.get().retrieve().bodyToFlux(User.class)

使用 Kotlin 和 Spring Framework 扩展,您可以编写以下代码:

val users = client.get().retrieve().bodyToFlux<User>()
// or (both are equivalent)
val users : Flux<User> = client.get().retrieve().bodyToFlux()

与 Java 中一样,Kotlin 中的users是强类型的,但是 Kotlin 的聪明类型推断允许使用较短的语法。

1.3. Null-safety

Kotlin 的主要功能之一是null-safety,它在编译时干净地处理null值,而不是在运行时撞到著名的NullPointerException。这通过可空性声明和表示“值或无值”的语义使应用程序更安全,而无需支付诸如Optional之类的包装器的费用。 (Kotlin 允许使用具有可为空值的函数构造.请参阅此Kotlin 空安全综合指南。)

尽管 Java 不允许您在其类型系统中表示空安全性,但 Spring 框架通过org.springframework.lang包中声明的对工具友好的 Comments 提供了整个 Spring Framework API 的 null 安全性。默认情况下,Kotlin 中使用的 Java API 中的类型被识别为platform types,对此它们的空检查得到了放宽。 Kotlin 对 JSR-305 注解的支持和 Spring 可空性 Comments 为 Kotlin 开发人员提供了整个 Spring Framework API 的空安全性,具有在编译时处理null相关问题的优势。

Note

诸如 Reactor 或 Spring Data 之类的库提供了空安全 API,以利用此功能。

您可以通过添加带有以下选项的-Xjsr305编译器标志来配置 JSR-305 检查:-Xjsr305={strict|warn|ignore}

对于 kotlin 1.1 版,默认行为与-Xjsr305=warn相同。必须使用strict值,才能从 Spring API 推断出的 Kotlin 类型中考虑到 Spring Framework API 的空安全性,但应在知道 Spring API 的空性声明即使在次要发行版之间可能会演变的情况下使用,并且应该在其中添加更多检查的前提下使用strict值。Future)。

Note

尚不支持泛型类型参数,varargs 和数组元素的可空性,但应在即将发布的版本中。有关最新信息,请参见this discussion

1.4. 类和接口

Spring 框架支持各种 Kotlin 构造,例如通过主构造函数实例化 Kotlin 类,不可变的类数据绑定以及使用默认值函数可选参数。

Kotlin 参数名称是通过专用的KotlinReflectionParameterNameDiscoverer识别的,该KotlinReflectionParameterNameDiscoverer允许查找接口方法参数名称,而无需在编译过程中启用 Java 8 -parameters编译器标志。

序列化或反序列化 JSON 数据所需的Jackson·Kotlin 模块在 Classpath 中找到时会自动注册,如果在没有 Jackson Jackson Kotlin 模块存在的情况下检测到 Jackson 和 Kotlin,则会记录一条警告消息。

您可以将配置类声明为顶层或嵌套但不内部,因为后者需要引用外部类。

1.5. Annotations

Spring 框架还利用Kotlin null-safety来确定是否需要 HTTP 参数,而不必显式定义required属性。这意味着@RequestParam name: String?被视为不是必需的,相反,@RequestParam name: String被视为是必需的。 Spring Messaging @HeaderComments 也支持此功能。

以类似的方式,使用@Autowired@Bean@Inject注入 Spring bean 会使用此信息来确定是否需要 bean。

例如,@Autowired lateinit var thing: Thing表示必须在应用程序上下文中注册类型Thing的 bean,而如果@Autowired lateinit var thing: Thing?不存在,则@Autowired lateinit var thing: Thing?不会引发错误。

按照相同的原理,@Bean fun play(toy: Toy, car: Car?) = Baz(toy, Car)表示类型Toy的 bean 必须在应用程序上下文中注册,而类型Car的 bean 可能存在或可能不存在。相同的行为适用于自动装配的构造函数参数。

Note

如果对具有属性或主要构造函数参数的类使用 bean 验证,则可能需要使用Comments 使用场所目标(例如@field:NotNull@get:Size(min=5, max=15)),如此堆栈溢出响应中所述。

1.6. Bean 定义 DSL

Spring Framework 5 通过使用 lambda 作为 XML 或 Java 配置(@Configuration@Bean)的替代方法,引入了一种以功能性方式注册 Bean 的新方法。简而言之,它使您可以使用充当FactoryBean的 lambda 注册 bean。该机制非常有效,因为它不需要任何反射或 CGLIB 代理。

在 Java 中,您可以例如编写以下内容:

GenericApplicationContext context = new GenericApplicationContext();
    context.registerBean(Foo.class);
    context.registerBean(Bar.class, () -> new Bar(context.getBean(Foo.class))
);

在 Kotlin 中,使用类型化参数和GenericApplicationContext Kotlinextensions,您可以 Rewrite 以下内容:

val context = GenericApplicationContext().apply {
    registerBean<Foo>()
    registerBean { Bar(it.getBean<Foo>()) }
}

为了允许使用更具声明性的方法和更简洁的语法,Spring Framework 提供了Kotlin bean 定义 DSL,它通过干净的声明性 API 声明了ApplicationContextInitializer,该 API 使您可以处理概要文件和Environment来自定义如何注册 Bean。以下示例创建一个Play配置文件:

fun beans() = beans {
    bean<UserHandler>()
    bean<Routes>()
    bean<WebHandler>("webHandler") {
        RouterFunctions.toWebHandler(
            ref<Routes>().router(),
            HandlerStrategies.builder().viewResolver(ref()).build()
        )
    }
    bean("messageSource") {
        ReloadableResourceBundleMessageSource().apply {
            setBasename("messages")
            setDefaultEncoding("UTF-8")
        }
    }
    bean {
        val prefix = "classpath:/templates/"
        val suffix = ".mustache"
        val loader = MustacheResourceTemplateLoader(prefix, suffix)
        MustacheViewResolver(Mustache.compiler().withLoader(loader)).apply {
            setPrefix(prefix)
            setSuffix(suffix)
        }
    }
    profile("play") {
        bean<Play>()
    }
}

在前面的示例中,bean<Routes>()使用构造函数自动装配,而ref<Routes>()applicationContext.getBean(Routes::class.java)的快捷方式。

然后,您可以使用此beans()函数在应用程序上下文中注册 bean,如以下示例所示:

val context = GenericApplicationContext().apply {
    beans().initialize(this)
    refresh()
}

Note

该 DSL 是编程的,这意味着它允许通过if表达式,for循环或任何其他 Kotlin 构造对 bean 进行自定义注册逻辑。

有关具体示例,请参见Spring kotlin 功能 bean 声明

Note

Spring Boot 基于 Java 配置和尚未为功能 Bean 定义提供特定支持,但是您可以通过 Spring Boot 的ApplicationContextInitializer支持实验性地使用功能性 bean 定义。有关更多详细信息和最新信息,请参见这个堆栈溢出答案

1.7. Web

1.7.1. WebFlux 功能性 DSL

Spring Framework 现在带有Kotlin 路由 DSL,可让您使用WebFlux 功能 API编写干净且惯用的 Kotlin 代码,如以下示例所示:

router {
    accept(TEXT_HTML).nest {
        GET("/") { ok().render("index") }
        GET("/sse") { ok().render("sse") }
        GET("/users", userHandler::findAllView)
    }
    "/api".nest {
        accept(APPLICATION_JSON).nest {
            GET("/users", userHandler::findAll)
        }
        accept(TEXT_EVENT_STREAM).nest {
            GET("/users", userHandler::stream)
        }
    }
    resources("/**", ClassPathResource("static/"))
}

Note

该 DSL 是编程的,这意味着它允许通过if表达式,for循环或任何其他 Kotlin 构造对 bean 进行自定义注册逻辑。当您需要根据动态数据(例如,来自数据库)注册路由时,这很有用。

有关具体示例,请参见MiXiT 项目 Route

1.7.2. Kotlin 脚本模板

从 4.3 版本开始,Spring 框架提供了一个ScriptTemplateView来通过使用脚本引擎来呈现模板。它支持JSR-223。 Spring Framework 5 进一步扩展了此功能到 WebFlux 并支持i18n 和嵌套模板

Kotlin 提供了类似的支持,并允许渲染基于 Kotlin 的模板。有关详情,请参见this commit

这启用了一些有趣的用例-例如通过使用kotlinx.html DSL 或使用带有插值的 Kotlin 多行String来编写类型安全的模板。

这可以让您在受支持的 IDE 中编写具有完全自动完成和重构支持的 Kotlin 模板,如以下示例所示:

import io.spring.demo.*

"""
${include("header")}
<h1>${i18n("title")}</h1>
<ul>
${users.joinToLine{ "<li>${i18n("user")} ${it.firstname} ${it.lastname}</li>" }}
</ul>
${include("footer")}
"""

有关更多详细信息,请参见kotlin-script-templating示例项目。

1.8. Kotlin 的 Spring 项目

本节提供了一些值得在 Kotlin 中开发 Spring 项目的特定提示和建议。

1.8.1. 默认为最终

默认情况下,Kotlin 的所有类均为期末考试。类上的open修饰符与 Java 的final相反:它允许其他人从此类继承。这也适用于成员函数,因为它们需要标记为open才能被覆盖。

尽管 Kotlin 的 JVM 友好设计通常与 Spring 毫无冲突,但如果不考虑这一事实,此 Kotlin 特定功能可能会阻止应用程序启动。这是因为 Spring Bean(例如出于技术原因需要在运行时继承的@Configuration类)通常由 CGLIB 代理。解决方法是在由 CGLIB 代理的 Spring bean 的每个类和成员函数上添加open关键字(例如@Configuration类),这会很快变得很痛苦,并且违反了保持代码简洁和可预测的 Kotlin 原则。

幸运的是,Kotlin 现在提供了一个kotlin-spring插件(kotlin-allopen插件的预配置版本),该插件会自动打开使用以下注解之一进行注解或元注解的类型的类及其成员函数:

支持元 Comments,这意味着将自动打开用@Configuration@Controller@RestController@Service@RepositoryComments 的类型,因为这些 Comments 用@Component进行了元 Comments。

start.spring.io默认启用它,因此,实际上,您可以编写 Java Bean,而无需使用其他open关键字,就像 Java 中一样。

1.8.2. 使用不可变的类实例进行持久化

在 Kotlin 中,在主构造函数中声明只读属性非常方便,并且被视为最佳实践,如以下示例所示:

class Person(val name: String, val age: Int)

您可以选择添加数据关键字,以使编译器自动从主要构造函数中声明的所有属性派生以下成员:

如下例所示,即使Person属性是只读的,也可以轻松更改各个属性:

data class Person(val name: String, val age: Int)

val jack = Person(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

常见的持久性技术(例如 JPA)要求使用默认的构造函数,以防止此类设计。幸运的是,此“默认构造函数地狱”现在有一种解决方法,因为 Kotlin 提供了kotlin-jpa插件,该插件可以为使用 JPA 注解的类生成合成的无参数构造函数。

如果您需要将这种机制用于其他持久性技术,则可以配置kotlin-noarg插件。

Note

从 Kay 发行版开始,Spring Data 支持 Kotlin 不可变类实例,并且如果该模块使用 Spring Data 对象 Map(例如 MongoDB,Redis,Cassandra 等),则不需要kotlin-noarg插件。

1.8.3. 注入依赖

我们的建议是尝试使用val只读(并且在可能时不可为空)properties来支持构造函数注入,如以下示例所示:

@Component
class YourBean(
    private val mongoTemplate: MongoTemplate,
    private val solrClient: SolrClient
)

Note

从 Spring Framework 4.3 开始,具有单个构造函数的类的参数将自动自动关联,这就是为什么在上面显示的示例中不需要显式的@Autowired constructor的原因。

如果确实需要使用字段注入,则可以使用lateinit var构造,如以下示例所示:

@Component
class YourBean {

    @Autowired
    lateinit var mongoTemplate: MongoTemplate

    @Autowired
    lateinit var solrClient: SolrClient
}

1.8.4. 注入配置属性

在 Java 中,您可以使用注解(例如@Value("${property}"))注入配置属性。但是,在 Kotlin 中,$是用于string interpolation的保留字符。

因此,如果您想在 Kotlin 中使用@ValueComments,则需要通过编写@Value("\${property}")来转义$字符。

另外,您可以pass 语句以下配置 Bean 来定制属性占位符前缀:

@Bean
fun propertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
    setPlaceholderPrefix("%{")
}

您可以使用配置 Bean 自定义使用${…}语法的现有代码(例如 Spring BootActuator 或@LocalServerPort),如以下示例所示:

@Bean
fun kotlinPropertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
    setPlaceholderPrefix("%{")
    setIgnoreUnresolvablePlaceholders(true)
}

@Bean
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()

Note

如果您使用 Spring Boot,则可以使用@ConfigurationProperties而不是@ValueComments。但是,目前,这仅适用于lateinit或可为空的var属性(建议使用前者),因为尚不支持由构造函数初始化的不可变类。有关更多详细信息,请参见有关不可变 POJO 的@ConfigurationProperties 绑定接口上的@ConfigurationProperties 绑定的这些问题。

1.8.5. Comments 数组属性

KotlinComments 与 JavaComments 大部分相似,但是数组属性(在 Spring 中广泛使用)的行为有所不同。如Kotlin documentation中所述,您可以省略value属性名称(与其他属性不同),并将其指定为vararg参数。

要理解这意味着什么,请以@RequestMapping(这是最广泛使用的 SpringComments 之一)为例。此 JavaComments 声明如下:

public @interface RequestMapping {

    @AliasFor("path")
    String[] value() default {};

    @AliasFor("value")
    String[] path() default {};

    RequestMethod[] method() default {};

    // ...
}

@RequestMapping的典型用例是将处理程序方法 Map 到特定的路径和方法。在 Java 中,可以为 Comments 数组属性指定一个值,该值将自动转换为数组。

这就是为什么可以写@RequestMapping(value = "/toys", method = RequestMethod.GET)@RequestMapping(path = "/toys", method = RequestMethod.GET)的原因。

但是,在 Kotlin 1.2 中,您必须写@RequestMapping("/toys", method = [RequestMethod.GET])@RequestMapping(path = ["/toys"], method = [RequestMethod.GET])(必须使用命名数组属性指定方括号)。

此特定method属性(最常见的一种)的替代方法是使用快捷方式 Comments,例如@GetMapping@PostMapping等。

Note

提醒:如果未指定@RequestMapping method属性,则将匹配所有 HTTP 方法,而不仅是GET一个。

1.8.6. Testing

本节介绍结合 Kotlin 和 Spring 框架进行的测试。

PER_CLASS Lifecycle

Kotlin 允许您在反引号(+249++250+`注解,这是一种非常适合 Kotlin。

现在,由于具有junit.jupiter.testinstance.lifecycle.default = per_class属性的junit-platform.properties文件,您可以将默认行为更改为PER_CLASS

以下是非静态方法的示例@BeforeAll@AfterAll注解:

class IntegrationTests {

  val application = Application(8181)
  val client = WebClient.create("http://localhost:8181")

  @BeforeAll
  fun beforeAll() {
    application.start()
  }

  @Test
  fun `Find all users on HTML page`() {
    client.get().uri("/users")
        .accept(TEXT_HTML)
        .retrieve()
        .bodyToMono<String>()
        .test()
        .expectNextMatches { it.contains("Foo") }
        .verifyComplete()
  }

  @AfterAll
  fun afterAll() {
    application.stop()
  }
}
Specification-like Tests

您可以使用 JUnit 5 和 Kotlin 创建类似规范的测试。以下示例显示了如何执行此操作:

class SpecificationLikeTests {

  @Nested
  @DisplayName("a calculator")
  inner class Calculator {
     val calculator = SampleCalculator()

     @Test
     fun `should return the result of adding the first number to the second number`() {
        val sum = calculator.sum(2, 4)
        assertEquals(6, sum)
     }

     @Test
     fun `should return the result of subtracting the second number from the first number`() {
        val subtract = calculator.subtract(4, 2)
        assertEquals(2, subtract)
     }
  }
}
Kotlin 中的 WebTestClient 类型推断问题

由于类型推断问题,您必须使用 Kotlin expectBodyextensions(例如.expectBody<String>().isEqualTo("toys")),因为它为 Java API 的 Kotlin 问题提供了一种解决方法。

另请参阅相关的SPR-16057问题。

1.9. 入门

本节描述了结合 Kotlin 和 Spring 框架的项目的最快入门方法。

1.9.1. 使用 start.spring.io

在 Kotlin 中启动新的 Spring Framework 5 项目的最简单方法是在start.spring.io上创建一个新的 Spring Boot 2 项目。

您还可以按照此博客文章所述创建独立的 WebFlux 项目。

1.9.2. 选择网络风味

Spring Framework 现在带有两个不同的 Web 堆栈:Spring MVCSpring WebFlux

如果您要创建处理延迟,长期连接,流方案的应用程序,或者要使用网络功能的 Kotlin DSL,建议使用 Spring WebFlux。

对于其他用例,尤其是在使用阻塞技术(例如 JPA),Spring MVC 及其基于 Comments 的编程模型的情况下,它是完全有效且完全受支持的选择。

1.10. Resources

我们建议以下资源供人们学习如何使用 Kotlin 和 Spring 框架构建应用程序:

1.10.1. Tutorials

我们建议以下教程:

1.10.2. 博客文章

以下博客文章提供了更多详细信息:

1.10.3. Examples

以下 Github 项目提供了示例,您可以从中学习甚至扩展:

1.10.4. Issues

以下列表对与 Spring 和 Kotlin 支持有关的未决问题进行了分类:

2. Apache Groovy

Groovy 是一种功能强大的,可选类型的动态语言,具有静态键入和静态编译功能。它提供了简洁的语法,并且可以与任何现有的 Java 应用程序顺利集成。

Spring 框架提供了专用的ApplicationContext,该ApplicationContext支持基于 Groovy 的 Bean 定义 DSL。有关更多详细信息,请参见Groovy Bean 定义 DSL

动态语言支持提供了对 Groovy 的进一步支持,包括用 Groovy 编写的 bean,可刷新的脚本 bean 等。

3.动态语言支持

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

Spring 当前支持以下动态语言:

为什么只有这些语言?

我们选择支持这些语言是因为:

您可以在Scenarios中找到可以立即使用这种动态语言支持的完整示例。

3.1. 第一个例子

本章的大部分内容与详细描述动态语言支持有关。在深入探讨动态语言支持的所有内容之前,我们来看一个使用动态语言定义的 bean 的简单示例。第一个 bean 的动态语言是 Groovy。 (该示例的基础取自 Spring 测试套件.如果要查看其他任何受支持语言的等效示例,请查看源代码)。

下一个示例显示了 Groovy bean 将要实现的Messenger接口。请注意,此接口是用纯 Java 定义的。注入对Messenger的引用的相关对象不知道基础实现是 Groovy 脚本。以下 Lists 显示了Messenger接口:

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

}

Note

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

有关基于架构的配置的更多信息,请参见基于 XML 模式的配置

最后,以下示例显示了将 Groovy 定义的Messenger实现注入到DefaultBookingService类的实例中的 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">

    <!-- 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 阅读有关前面配置的原因和原因的详细信息。

3.2. 定义由动态语言支持的 Bean

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

注意,本章并不试图解释受支持的动态语言的语法和惯用语。例如,如果您想使用 Groovy 在您的应用程序中编写某些类,我们假设您已经了解 Groovy。如果您需要有关动态语言本身的更多详细信息,请参阅本章末尾的Further Resources

3.2.1. 共同概念

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

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

<lang:language/>元素

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

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

可用于配置的确切属性和子元素完全取决于定义该 bean 所使用的语言(本章稍后的特定于语言的部分对此进行了详细说明)。

Refreshable Beans

Spring 对动态语言的支持(也许是唯一)中最引人注目的增值之一就是“可刷新 bean”功能。

可刷新的 bean 是动态语言支持的 bean。通过少量配置,支持动态语言的 Bean 可以监视其基础源文件资源中的更改,然后在动态语言源文件更改时重新加载自身(例如,当您在 Windows 上编辑并保存对文件的更改时)。文件系统)。

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

Note

默认情况下,此功能是关闭的。

现在我们来看一个例子,看看开始使用可刷新 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 上触发刷新。

以下 Lists 显示了此示例应用程序:

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()方法的所有调用,以使消息用引号引起来。以下 Lists 显示了您(开发人员)应在程序执行暂停时对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。使用内联源代码只是一时的工作。 (有关此类示例,请参见Scripted Validators。)

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

关于 Spring 的动态语言支持,有一件非常重要的事情要注意。即,您不能(当前)向动态语言支持的 bean 提供构造函数参数(因此,构造函数注入不适用于动态语言支持的 bean)。为了使对构造函数和属性的特殊处理 100%清晰,以下代码和配置的混合不起作用:

例子 1.一种行不通的方法

// 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>

实际上,这种限制并不像它最初出现的那样重要,因为二传手注入是绝大多数开发人员所偏爱的注入方式(我们将讨论是否对另一天来说是一件好事)。

3.2.2. 杂色 bean

本节描述如何在 Spring 中使用 Groovy 中定义的 bean。

Groovy 库依赖

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

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
    }

}

以下 bean 定义使用 Groovy 中定义的计算器:

<-- 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(毫不奇怪)。 (有关更多有趣的示例,请参见动态语言展示项目以获取更复杂的示例,或参见本章稍后的示例Scenarios)。

每个 Groovy 源文件中定义的类不得超过一个。尽管这在 Groovy 中是完全合法的,但(可以说)这是一种不好的做法。为了采用一致的方法,您(在 Spring 小组看来)应该遵守每个源文件一个(公共)类的标准 Java 约定。

通过使用回调自定义 Groovy 对象

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

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对象。

3.2.3. BeanShellbean

本节描述如何在 Spring 中使用 BeanShell bean。

BeanShell 库依赖项

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

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接口。我们再次显示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,该 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 的某些方案,请参见Scenarios

3.3. Scenarios

用脚本语言定义 Spring 托管 Bean 会带来好处的可能方案有很多,而且有很多。本节描述了 Spring 中对动态语言支持的两种可能的用例。

3.3.1. 脚本化 Spring MVC 控制器

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

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

Note

要对动态语言支持的 bean 进行任何更改的这种自动“拾取”,必须启用“可刷新 bean”功能。有关此功能的完整说明,请参见Refreshable Beans

以下示例显示了使用 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>

3.3.2. 脚本验证器

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

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

Note

要对支持动态语言的 bean 进行任何更改的自动“提取”,必须启用“可刷新 bean”功能。有关此功能的完整和详细说明,请参见Refreshable Beans

以下示例显示了使用 Groovy 动态语言实现的 Spring org.springframework.validation.Validator(有关Validator接口的讨论,请参见使用 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.")
    }

}

3.4. 额外细节

最后一部分包含与动态语言支持有关的一些其他详细信息。

3.4.1. AOP —建议脚本化 Bean

您可以使用 Spring AOP 框架来建议脚本化的 bean。实际上,Spring AOP 框架没有意识到建议使用的 Bean 可能是脚本 Bean,因此您使用(或打算使用)的所有 AOP 用例和功能都可以与脚本 Bean 一起使用。当建议脚本 bean 时,不能使用基于类的代理。您必须使用interface-based proxies

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

3.4.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>

有关 Spring 框架中范围支持的完整讨论,请参见IoC 容器中的Bean scopes

3.4.3. lang XML 模式

Spring XML 配置中的lang元素用于将以动态语言(例如 JRuby 或 Groovy)编写的对象暴露为 Spring 容器中的 bean。

这些元素(以及动态语言支持)在动态语言支持中全面介绍。有关此支持和lang元素的完整详细信息,请参见该章。

要使用lang模式中的元素,您需要在 Spring XML 配置文件的顶部具有以下序言。以下代码段中的文本引用了正确的架构,以便您可以使用lang名称空间中的标记:

<?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">

    <!-- bean definitions here -->

</beans>

3.5. 更多资源

以下链接提供了有关本章中描述的各种动态语言的更多资源:

首页