语言
1. Kotlin
Spring 框架为 Kotlin 提供了一流的支持,并允许开发者编写 Kotlin 应用几乎就像 Spring 框架本身就是原生的 Kotlin 框架一样。 参考文档中的大多数代码示例是 除了Java之外,还提供Kotlin版本。
用Kotlin构建Spring应用最简单的方法是利用Spring Boot和 它专门支持Kotlin。这个全面的教程将教你如何使用 Kotlin 构建 Spring Boot 应用程序 start.spring.io。
欢迎加入Kotlin Slack的 #spring 频道,或者向以下平台提问Spring和Kotlin如果需要支持,可以在Stackoverflow上使用标签。
1.1. 要求
Spring Framework 支持 Kotlin 1.3+,并且需要Kotlin-Stdlib(或其变体之一,如Kotlin-stdlib-JDK8)
和Kotlin反射在阶级路径上保持存在感。如果你在 start.spring.io 上启动Kotlin项目,这些插件是默认提供的。
| Kotlin 的在线类尚未支持。 |
Jackson Kotlin模块是必修的
用于用 Jackson 序列化或反序列化 Kotlin 类的 JSON 数据,所以一定要添加com.fasterxml.jackson.module:jackson-module-kotlin如果你有这样的需求,可以依赖你的项目。
在类路径中发现时会自动注册。 |
1.2. 扩展
Kotlin 扩展提供了以下能力 以扩展现有类并附加更多功能。Spring Framework Kotlin API 利用这些扩展为现有的 Spring API 添加新的 Kotlin 专用便利功能。
Spring Framework KDoc API 列出了以下内容 并记录所有可用的Kotlin扩展和DSL。
请记住,Kotlin扩展需要导入才能使用。这意味着,
例如,当GenericApplicationContext.registerBeanKotlin 扩展
只有当org.springframework.context.support.registerBean是进口的。
不过,和静态导入类似,IDE在大多数情况下应该会自动建议导入。 |
例如,Kotlin的具体化类型参数为JVM泛型类型擦除提供了一种变通方法,
而 Spring Framework 也提供了一些扩展以利用这一特性。
这使 Kotlin API 更加完善Rest模板,对于新的Web客户端来自Spring
WebFlux,以及其他各种API的应用。
| 其他库,如 Reactor 和 Spring Data,也提供了 Kotlin 扩展 因此整体上提供了更好的 Kotlin 开发体验。 |
检索用户在 Java 中,你通常会写以下内容:
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()
如同爪哇语,用户在 Kotlin 中 是强类型,但 Kotlin 巧妙的类型推断允许
用于简短的语法。
1.3. 零安全
Kotlin的一个关键特性是零安全,
该系统清晰地处理了零汇编时的价值,而不是遇到著名的NullPointerException在运行时。这通过可空性使应用更安全
声明并表达“有价值或无价值”语义,而无需支付封装器成本,例如自选.
(Kotlin 允许使用具有可空值的函数构造。请参阅这份关于Kotlin零安全系统的全面指南。)
虽然 Java 不允许在类型系统中表达空安全,但 Spring 框架
通过在org.springframework.lang包。
默认情况下,Kotlin 中使用的 Java API 类型被识别为平台类型,
对此,空检查被放宽。Kotlin 对 JSR-305 注释的支持以及 Spring 空标注为整个 Spring Framework API 提供了空安全保障,供 Kotlin 开发者使用,
其优势在于处理零-编译时相关问题。
| 像 Reactor 或 Spring Data 这样的库提供了无安全 API 来利用这一特性。 |
您可以通过添加以下内容来配置JSR-305检查-Xjsr305编译器标志,包含以下
选项:-Xjsr305={strict|warn|ignore}.
对于Kotlin 1.1+版本,默认行为相同-Xjsr305=警告.
这严格值必须考虑 Spring Framework API 的空安全措施
在Kotlin类型中,这些类型是从Spring API推断出来的,但应在Spring
API 的空可言声明即使在次级版本之间也可能演进,且可能有更多检查
未来会加入。
| 通用类型参数、vararg 和数组元素的空可性尚未被支持, 但应该会在即将发布的版本中出现。请参阅此讨论以获取最新信息。 |
1.4. 类与接口
Spring 框架支持各种 Kotlin 结构,例如实例化 Kotlin 类 通过主构造子、不可变类、数据绑定和函数可选参数 默认值。
Kotlin参数名称通过专用的识别KotlinReflectionParameterNameDiscoverer,
这使得无需Java 8的接口方法参数名称即可查找-参数编译器标志将在编译过程中启用。
你可以声明配置类为顶层或嵌套,但不能声明内部, 因为后者需要引用外类。
1.5. 注释
Spring 框架还利用了 Kotlin 的空安全机制,在无需显式
定义必填属性。那就是说@RequestParam名字:弦?被处理
不要求,反过来,@RequestParam名:弦被视为必需。
该功能也支持春季消息系统@Header注解。
类似地,春豆注射@Autowired,@Bean或@Inject使用
这些信息用来判断是否需要豆子。
例如@Autowired lateinit var thing: Thing意味着一颗豆子
类型东西必须在应用上下文中注册,而@Autowired lateinit var thing: thing?如果不存在这样的豆子,则不会产生错误。
遵循同样的原则,@Bean 趣味游戏(玩具:玩具,汽车:汽车?)= Baz(玩具,汽车)意味 着
那是个小人物玩具必须在应用上下文中注册,而
类型汽车可能存在也可能不存在。同样的行为也适用于自动接线构造器参数。
如果你对带有属性或主构造子的类使用豆子验证
参数,你可能需要使用注释使用-站点目标,
如@field:非虚无或@get:Size(min=5, max=15)如本 Stack Overflow 回答所述。 |
1.6. Beans定义DSL
Spring Framework 通过使用 lambda 支持以功能性方式注册豆子
作为XML或Java配置的替代方案(@Configuration和@Bean).简而言之
它让你用一个lambda注册豆子,这个lambda作为一个工厂豆.
这种机制非常高效,因为它不需要任何反射或CGLIB代理。
在 Java 中,你可以写以下内容:
class Foo {}
class Bar {
private final Foo foo;
public Bar(Foo foo) {
this.foo = foo;
}
}
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(Foo.class);
context.registerBean(Bar.class, () -> new Bar(context.getBean(Foo.class)));
在Kotlin中,具有实体化型参数和通用应用上下文Kotlin 扩展,
你可以写以下内容:
class Foo
class Bar(private val foo: Foo)
val context = GenericApplicationContext().apply {
registerBean<Foo>()
registerBean { Bar(it.getBean()) }
}
当班级开始时酒吧只有一个构造子,你甚至可以直接指定 bean 类,
构造器参数将按类型自动接线:
val context = GenericApplicationContext().apply {
registerBean<Foo>()
registerBean<Bar>()
}
为了支持更声明式的方法和更简洁的语法,Spring Framework 提供了以下条件
a Kotlin bean 定义 DSL 它声明一个应用上下文初始化器通过一个干净的声明式API,
这样你就能处理配置文件和环境用于定制
豆子是如何注册的。
在以下示例中请注意:
-
类型推断通常允许避免为豆子引用指定类型,例如
参考(“bazBean”) -
可以使用Kotlin顶层函数声明豆子,使用可调用的引用,如
bean(::我的路由器)在这个例子中 -
在指定时
豆<条>()或bean(::我的路由器)参数按类型自动接线 -
这
FooBar只有当福巴尔个人资料已激活
class Foo
class Bar(private val foo: Foo)
class Baz(var message: String = "")
class FooBar(private val baz: Baz)
val myBeans = beans {
bean<Foo>()
bean<Bar>()
bean("bazBean") {
Baz().apply {
message = "Hello world"
}
}
profile("foobar") {
bean { FooBar(ref("bazBean")) }
}
bean(::myRouter)
}
fun myRouter(foo: Foo, bar: Bar, baz: Baz) = router {
// ...
}
该DSL是程序化的,意味着它允许对豆子进行自定义注册逻辑
通过一个如果表达式,a为循环,或任何其他Kotlin构造。 |
然后你可以用这个豆()函数用于在应用上下文中注册豆子,
如下示例所示:
val context = GenericApplicationContext().apply {
myBeans.initialize(this)
refresh()
}
Spring Boot 基于 JavaConfig,尚未提供功能性豆定义的具体支持
但你可以通过 Spring Boot 实验性地使用函数式豆的定义应用上下文初始化器支持。
更多细节和最新信息请参见Stack Overflow的回答。另见Spring Fu孵化器开发的实验性Kofu DSL。 |
1.7. 网络
1.7.1. 路由器DSL
Spring Framework 自带了三种版本的 Kotlin 路由器 DSL:
-
WebMvc.fn DSL 与路由器 { }
-
WebFlux.fn Coroutines DSL with coRouter { }
这些DSL允许你编写干净且符合惯用法的Kotlin代码,构建一个路由器功能实例如下所示:
@Configuration
class RouterRouterConfiguration {
@Bean
fun mainRouter(userHandler: UserHandler) = 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/"))
}
}
该DSL是程序化的,意味着它允许对豆子进行自定义注册逻辑
通过一个如果表达式,a为循环,或任何其他Kotlin构造。这很有用
当你需要根据动态数据(例如数据库)注册路由时, |
具体例子请参见MiXiT项目。
1.7.2. 模拟Mvc DSL
Kotlin DSL 通过以下方式提供莫克麦克Kotlin 扩展以提供更
采用惯用的Kotlin API,并允许更易发现(不使用静态方法)。
val mockMvc: MockMvc = ...
mockMvc.get("/person/{name}", "Lee") {
secure = true
accept = APPLICATION_JSON
headers {
contentLanguage = Locale.FRANCE
}
principal = Principal { "foo" }
}.andExpect {
status { isOk }
content { contentType(APPLICATION_JSON) }
jsonPath("$.name") { value("Lee") }
content { json("""{"someBoolean": false}""", false) }
}.andDo {
print()
}
1.7.3. Kotlin 脚本模板
通过杠杆Scripting-jsr223依赖关系,它
可以使用该功能渲染基于Kotlin的模板,并kotlinx.html DSL或Kotlin多行插值字符串.
build.gradle.kts
dependencies {
runtime("org.jetbrains.kotlin:kotlin-scripting-jsr223:${kotlinVersion}")
}
配置通常通过以下方式完成ScriptTemplateConfigurer和ScriptTemplateViewResolver豆。
KotlinScriptConfiguration.kt
@Configuration
class KotlinScriptConfiguration {
@Bean
fun kotlinScriptConfigurer() = ScriptTemplateConfigurer().apply {
engineName = "kotlin"
setScripts("scripts/render.kts")
renderFunction = "render"
isSharedEngine = false
}
@Bean
fun kotlinScriptViewResolver() = ScriptTemplateViewResolver().apply {
setPrefix("templates/")
setSuffix(".kts")
}
}
参见 kotlin-script-templating 示例 查看更多详情。
1.7.4. Kotlin 多平台序列化
自 Spring Framework 5.3 起,Kotlin 的多平台序列化为 支持于Spring MVC、Spring WebFlux和Spring Messaging(RSocket)。内置支持目前仅针对 JSON 格式。
要启用它,请按照这些说明添加相关的依赖和插件。
使用 Spring MVC 和 WebFlux 时,如果 Kotlin 序列化和 Jackson 都处于类路径中,默认配置为
Kotlin 序列化设计仅序列化注释为@Serializable.
使用 Spring 消息(RSocket),如果你想要自动配置,确保 Jackson、GSON 或 JSONB 都不在类路径中,
如果需要 Jackson 配置KotlinSerializationJsonMessageConverter手动下载。
1.8. 协程
Kotlin 协程是 Kotlin
轻量线程允许以命令式方式编写非阻塞代码。在语言方面,
挂起函数为异步作提供了抽象,而库端则提供了 kotlinx.coroutines 这样的函数异步 { }以及像这样的类型流.
Spring Framework 支持以下范围内的协程:
-
Spring MVC 和 WebFlux 注释中的挂起功能支持
@Controller -
WebFlux.fn coRouter { } DSL
-
悬挂函数和
流RSocket 中的支持@MessageMapping注释方法
1.8.1. 依赖关系
协程支持在Kotlinx-协程-核心和Kotlinx协程反应器依赖关系位于类路径中:
build.gradle.kts
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${coroutinesVersion}")
}
版本1.4.0和上述 都被支持。
1.8.2. Reactive 如何转化为协程?
对于返回值,从响应式API转换为协程API的过程如下:
-
趣味控者():Mono<虚空>成为暂停乐趣处理员() -
趣味操作员():单<T>成为暂停乐趣管理员(): T或暂停乐趣管理员():T?这取决于是否单可以是空也可以不是(优点是更静态类型) -
趣味控者():Flux<T>成为趣味控者():Flow<T>
对于输入参数:
-
如果不需要懒惰,
趣味作者(单色:单色单色系<T>)成为趣味操作员(数值:T)由于可以调用悬挂函数来获得值参数。 -
如果需要懒惰,
趣味作者(单色:单色单色系<T>)成为趣味处理员(提供商:暂停 ()→T 的或趣味处理员(提供商:暂停()T→)
流是通量在协程世界中,这相当于热流或冷流、有限流或无限流,主要区别如下:
阅读这篇关于《用 Spring、协程和 Kotlin Flow 进行反应式》的博客文章,了解更多细节,包括如何与协程并发运行代码。
1.8.3. 控制器
这里有一个协程的例子@RestController.
@RestController
class CoroutinesRestController(client: WebClient, banner: Banner) {
@GetMapping("/suspend")
suspend fun suspendingEndpoint(): Banner {
delay(10)
return banner
}
@GetMapping("/flow")
fun flowEndpoint() = flow {
delay(10)
emit(banner)
delay(10)
emit(banner)
}
@GetMapping("/deferred")
fun deferredEndpoint() = GlobalScope.async {
delay(10)
banner
}
@GetMapping("/sequential")
suspend fun sequential(): List<Banner> {
val banner1 = client
.get()
.uri("/suspend")
.accept(MediaType.APPLICATION_JSON)
.awaitExchange()
.awaitBody<Banner>()
val banner2 = client
.get()
.uri("/suspend")
.accept(MediaType.APPLICATION_JSON)
.awaitExchange()
.awaitBody<Banner>()
return listOf(banner1, banner2)
}
@GetMapping("/parallel")
suspend fun parallel(): List<Banner> = coroutineScope {
val deferredBanner1: Deferred<Banner> = async {
client
.get()
.uri("/suspend")
.accept(MediaType.APPLICATION_JSON)
.awaitExchange()
.awaitBody<Banner>()
}
val deferredBanner2: Deferred<Banner> = async {
client
.get()
.uri("/suspend")
.accept(MediaType.APPLICATION_JSON)
.awaitExchange()
.awaitBody<Banner>()
}
listOf(deferredBanner1.await(), deferredBanner2.await())
}
@GetMapping("/error")
suspend fun error() {
throw IllegalStateException()
}
@GetMapping("/cancel")
suspend fun cancel() {
throw CancellationException()
}
}
使用一个@Controller也支持。
@Controller
class CoroutinesViewController(banner: Banner) {
@GetMapping("/")
suspend fun render(model: Model): String {
delay(10)
model["banner"] = banner
return "index"
}
}
1.8.4. WebFlux.fn
这里是一个通过 coRouter { } DSL 及相关处理程序定义的协程路由器示例。
@Configuration
class RouterConfiguration {
@Bean
fun mainRouter(userHandler: UserHandler) = coRouter {
GET("/", userHandler::listView)
GET("/api/user", userHandler::listApi)
}
}
class UserHandler(builder: WebClient.Builder) {
private val client = builder.baseUrl("...").build()
suspend fun listView(request: ServerRequest): ServerResponse =
ServerResponse.ok().renderAndAwait("users", mapOf("users" to
client.get().uri("...").awaitExchange().awaitBody<User>()))
suspend fun listApi(request: ServerRequest): ServerResponse =
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyAndAwait(
client.get().uri("...").awaitExchange().awaitBody<User>())
}
1.8.5. 交易
协程上的事务通过Reactive的程序变体支持。 事务管理自 Spring Framework 5.2 起提供。
对于悬挂函数,aTransactionalOperator.executeAndAwait提供延期。
import org.springframework.transaction.reactive.executeAndAwait
class PersonRepository(private val operator: TransactionalOperator) {
suspend fun initDatabase() = operator.executeAndAwait {
insertPerson1()
insertPerson2()
}
private suspend fun insertPerson1() {
// INSERT SQL statement
}
private suspend fun insertPerson2() {
// INSERT SQL statement
}
}
为了Kotlin流一个Flow<T>.transactional提供延期。
import org.springframework.transaction.reactive.transactional
class PersonRepository(private val operator: TransactionalOperator) {
fun updatePeople() = findPeople().map(::updatePerson).transactional(operator)
private fun findPeople(): Flow<Person> {
// SELECT SQL statement
}
private suspend fun updatePerson(person: Person): Person {
// UPDATE SQL statement
}
}
1.9. Kotlin的春季项目
本节提供了一些针对春季项目开发的具体提示和建议 在Kotlin。
1.9.1. 默认最终
默认情况下,Kotlin的所有课程最后.
这打开类上的修饰符与Java的相反最后:这让其他人能够继承这份工作
类。这同样适用于成员函数,因为它们需要标记为打开被覆盖。
虽然Kotlin的JVM友好设计通常与Spring无摩擦,但这一特定Kotlin特性如果不考虑这一点,可能会阻止应用启动。这是因为Spring beans(例如@Configuration默认需要在运行时因技术原因扩展的注释类通常由CGLIB代理。解决方法是添加一个打开每个类和Spring beans 的成员函数由 CGLIB 代理,这可能很快变得痛苦,且违背了 Kotlin 保持代码简洁和可预测的原则。
也可以通过以下方式避免配置类的CGLIB代理@Configuration(proxyBeanMethods = false). 看proxyBeanMethodsJavadoc更多细节请阅读。 |
幸运的是,Kotlin提供了Kotlin-斯普林插件(预配置的Kotlin-奥尔开放插件)会自动打开类以及其成员函数,用于用于注释或元注释的类型,这些类型通常用以下之一 附注:
-
@Component -
@Async -
@Transactional -
@Cacheable
元注释支持意味着标注为@Configuration,@Controller,@RestController,@Service或@Repository由于这些注释被元注释为@Component.
start.spring.io 使 这Kotlin-斯普林默认是插件。所以,实际上你可以写你的Kotlin Beans无需额外添加打开关键词,如Java。
Spring Framework 文档中的 Kotlin 代码示例并未明确说明打开在类及其成员函数上。这些示例是为项目编写的使用Kotlin-奥尔开放插件,因为这是最常用的配置。 |
1.9.2. 使用不可变类实例实现持久化
在Kotlin中,声明只读属性是方便且被认为是最佳实践在主构造子内,如下示例:
class Person(val name: String, val age: Int)
你可以选择添加这数据关键词为了使编译器自动从主构造器中宣告的所有属性推导出以下成员:在主构造器中:
-
等值()和hashCode() -
toString()形式为“用户(姓名=约翰,年龄=42岁)” -
componentN()这些函数在其声明顺序中对应于属性 -
复制()功能
正如下例所示,这允许对单个属性进行轻易修改,即使人属性为只读:
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注释的类生成合成无arg构造器。
如果你需要利用这种机制来支持其他持久化技术,可以配置 这Kotlin-诺阿尔格插件。
截至Kay发布列车,Spring Data支持Kotlin不可变类实例,且不需要Kotlin-诺阿尔格如果模块使用 Spring Data 对象映射,则该插件(例如 MongoDB、Redis、Cassandra 等)。 |
1.9.3. 添加依赖
我们的建议是尽量优先使用构造体注入瓦尔只读(且在可能的情况下不可空)属性,如下示例所示:
@Component
class YourBean(
private val mongoTemplate: MongoTemplate,
private val solrClient: SolrClient
)
只有一个构造函数的类的参数会自动被自动接线。这就是为什么不需要显式@Autowired构造器在所示的例子中 以上。 |
如果你真的需要用场注入,可以用lateinit var构建 如下示例所示:
@Component
class YourBean {
@Autowired
lateinit var mongoTemplate: MongoTemplate
@Autowired
lateinit var solrClient: SolrClient
}
1.9.4. 配置属性注入
在 Java 中,你可以通过注释(例如)注入配置属性@Value(“${property}”)). 然而,在Kotlin中, 是一个保留字符,用于字符串插值。$
因此,如果你想使用该系统@Value在Kotlin中,你需要通过写入逃离字符$@Value(“\${property}”).
如果你用Spring Boot,应该用@ConfigurationProperties而不是@Value附注。 |
作为替代方案,你可以通过声明以下配置 BEANS 来自定义属性占位符前缀以下配置豆:
@Bean
fun propertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
setPlaceholderPrefix("%{")
}
你可以自定义现有代码(比如Spring Boot执行器,或者@LocalServerPort)
该 使用${…}语法,带有配置豆,如下示例所示:
@Bean
fun kotlinPropertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
setPlaceholderPrefix("%{")
setIgnoreUnresolvablePlaceholders(true)
}
@Bean
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()
1.9.5. 检查异常
Java 和 Kotlin 的例外处理非常接近,主要区别在于 Kotlin 将所有异常视为
未被检查的例外情况。然而,当使用代理对象(例如类或方法)时
注释为@Transactional),投掷的检查异常默认会被
一未声明可抛弃异常.
要像 Java 一样投掷原始异常,方法应注释为@Throws明确指定投掷的检查异常(例如)@Throws(IOException::class)).
1.9.6. 注释数组属性
Kotlin 注释大多类似于 Java 注释,但数组属性(包括
在春季广泛使用)的行为有所不同。正如Kotlin文档中解释的,你可以省略
这值与其他属性不同,并指定为瓦拉格参数。
要理解这意味着什么,请考虑@RequestMapping(这是最广泛的之一
以 Spring 注释为例。该 Java 注释声明如下:
public @interface RequestMapping {
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
// ...
}
典型的使用场景@RequestMapping是将处理方法映射到特定路径
以及方法。在 Java 中,你可以为注释数组属性指定一个值,
并且它会自动转换为数组。
这就是为什么可以写作@RequestMapping(value = “/toys”, method = RequestMethod.GET)或@RequestMapping(path = “/toys”, method = RequestMethod.GET).
然而,在Kotlin中,你必须写@RequestMapping(“/toys”, method = [RequestMethod.GET])或@RequestMapping(path = [“/toys”], method = [RequestMethod.GET])(方括号需要
需要用命名数组属性来指定)。
针对此的替代方案方法属性(最常见的属性)为
使用快捷键注释,例如@GetMapping,@PostMapping,以及其他。
如果@RequestMapping 方法属性未指定,所有HTTP方法均未指定
匹配,而不仅仅是获取方法。 |
1.9.7. 测试
| 如果你正在使用Spring Boot,请参见相关文档。 |
构造器注入
正如专门部分所述,
JUnit 5 允许构建器注入豆子,这对 Kotlin 非常实用
以便使用瓦尔而不是lateinit var.你可以使用@TestConstructor(autowireMode = AutowireMode.ALL)以启用所有参数的自动接线。
@SpringJUnitConfig(TestConfig::class)
@TestConstructor(autowireMode = AutowireMode.ALL)
class OrderServiceIntegrationTests(val orderService: OrderService,
val customerService: CustomerService) {
// tests that use the injected OrderService and CustomerService
}
PER_CLASS生命周期
Kotlin 允许你在回溯列()之间指定有意义的测试函数名称。
截至JUnit 5,Kotlin测试类可以使用`@TestInstance(TestInstance.Lifecycle.PER_CLASS)注释以实现测试类的单实例化,从而允许使用@BeforeAll和@AfterAll非静态方法的注释,这与Kotlin很合适。
你也可以把默认行为改成PER_CLASS感谢junit-platform.properties(junit-platform.properties)文件中包含junit.jupiter.testinstance.lifecycle.default = per_class财产。
以下示例展示了@BeforeAll和@AfterAll非静态方法的注释:
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
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()
}
}
类规范测试
你可以用 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)
}
}
}
1.10. 入门
学习如何用Kotlin构建Spring应用最简单的方法是按照专门的教程作。
1.10.1.start.spring.io
在 Kotlin 中启动新 Spring Framework 项目最简单的方法是创建一个新的 Spring 启动2项目 start.spring.io。
1.10.2. 选择网页风格
Spring Framework 现在包含两个不同的网页栈:Spring MVC 和 Spring WebFlux。
如果你想开发能够应对延迟的应用程序,推荐使用 Spring WebFlux, 长寿命连接、流媒体场景,或者如果你想使用网页功能 Kotlin DSL。
对于其他场景,尤其是你使用JPA、Spring等阻断技术的话 MVC 及其基于注释的编程模型是推荐的选择。
1.11. 资源
我们推荐以下资源给学习如何构建应用程序的人 Kotlin与Spring框架:
-
Kotlin Slack(设有专用 #spring 频道)
1.11.1. 示例
以下Github项目提供了你可以学习甚至扩展的示例:
-
spring-boot-kotlin-demo:常规 Spring Boot 和 Spring Data JPA 项目
-
mixit:Spring Boot 2、WebFlux 和响应式 Spring Data MongoDB
-
spring-kotlin-functional:独立WebFlux和functional bean定义DSL
-
spring-kotlin-fullstack:WebFlux Kotlin 全栈示例,前端使用 Kotlin2js 代替 JavaScript 或 TypeScript
-
spring-petclinic-kotlin:Spring PetClinic 示例应用的 Kotlin 版本
-
spring-kotlin-deepdive:Boot 1.0 和 Java 从 Boot 2.0 和 Kotlin 迁移的逐步指南
-
spring-cloud-gcp-kotlin-app-sample: Spring Boot with Google Cloud Platform Integrations
2. 阿帕奇节奏
Groovy 是一种强大的、可选类型和动态类型语言,具有静态类型和静态类型 编译功能。它提供简洁的语法,并能与任何语言流畅整合 现有的Java应用程序。
Spring Framework 提供了一个专用的应用上下文支持基于Groovy的
豆子定义 DSL。更多详情请参见《The Groovy Bean Definition DSL》。
对Groovy的进一步支持,包括用Groovy编写的豆子、可刷新的文字豆, 动态语言支持中提供更多内容。
3. 动态语言支持
Spring 为使用已被 通过使用动态语言(如 Groovy)与 Spring 来定义。这种支撑使得 你可以用支持的动态语言编写任意数量的类,并且拥有 Spring 容器透明地实例化、配置并注入生成的 对象。
Spring 的脚本支持主要针对 Groovy 和 BeanShell。除此之外 具体支持的语言包括JSR-223脚本机制 以便与任何支持 JSR-223 的语言提供商集成(截至 Spring 4.2), 例如JRuby。
你可以找到这种动态语言支持的完整示例 在场景中立刻有用。
3.1. 第一个例子
本章的大部分内容是描述 在 细节。在深入探讨动态语言支持的所有细节之前, 我们快速示例一个动态语言定义的豆子。动态 这颗第一颗豆子的语言是Groovy。(本例的基础取自 春季测试套间。如果你想在其他任何游戏中看到类似的例子 支持的语言,可以看看源代码)。
下一个例子展示了信使界面,Groovy 豆子将要去那里
实现。注意,该接口是用纯Java定义的。依赖对象
以引用信使不知道底层的底层
实现是Groovy脚本。以下列表显示了信使接口:
package org.springframework.scripting;
public interface Messenger {
String getMessage();
}
以下示例定义了一个类,其依赖于信使接口:
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 中的界面:
// 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
}
|
要使用自定义动态语言标签来定义动态语言支持的豆子,你
需要在你的 Spring XML 配置文件顶部放置 XML 模式前言。
你还需要用Spring 有关基于模式配置的更多信息,请参见基于模式的 XML 配置。 |
最后,以下示例展示了影响
Groovy定义信使实现到一个实例中DefaultBookingService类:
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang https://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>
这预订服务豆(aDefaultBookingService)现在可以使用其私有信使成员变量为正常值,因为信使被注入的实例是
一个信使实例。这里没什么特别的——就是普通的Java和
纯粹的Groovy。
希望前面的XML片段已经说明清楚,但如果不够也不必过于担心。 继续阅读,深入了解上述配置的原因和原因。
3.2. 定义由动态语言支持的豆子
本节详细说明了如何定义春季管理Beans在任何 支持动态语言。
请注意,本章不试图解释所支持的语法和习语 动态语言。比如,如果你想用Groovy来写某些类 在你的申请中,我们假设你已经认识Groovy。如果你需要更多细节 关于动态语言本身,请参见 本章。
3.2.1. 常见概念
使用动态语言支持豆子的步骤如下:
-
为动态语言源代码编写测试(自然地)。
-
然后写动态语言的源代码本身。
-
通过适当的方式定义你的动态语言支持豆子
<语言/>XML 配置中的元素(你可以通过编程方式定义此类豆子,如下 使用 Spring API,但你需要查阅源代码 关于如何实现这一点的说明,因为本章未涵盖此类高级配置)。 注意这是一个迭代步骤。每种动态至少需要一个Beans定义 语言源文件(尽管多个 BEAN 定义可以引用同一个源文件)。
前两步(测试和编写动态语言源文件)已经超出预期 本章的范围。请参阅语言规范和参考手册 针对你选择的动态语言,继续开发你的动态语言 源文件。不过你首先要读完本章的剩余部分,因为 Spring 的动态语言支持确实对内容做出一些(小)假设 你动态语言源文件的。
<lang:language/>元素
上一节列表中的最后一步是定义动态语言支持的豆子定义,每个定义一个,对应你
想要配置(这和普通的 JavaBean 配置没什么不同)。然而
而不是指定将要为的类别的完全限定类名称
由容器实例化和配置,你可以使用<语言/>元素来定义动态语言支持的豆子。
每种支持的语言都有对应的<语言/>元素:
-
<语言:酷炫/>(酷炫) -
<语言:bsh/>(豆壳) -
<语言:std/>(JSR-223,例如与JRuby合作)
可配置的具体属性和子元素取决于 豆子具体定义在哪种语言中(语言特定部分) 本章后面会详细说明)。
可补充豆
动态语言最具吸引力的附加价值之一(也许是唯一最吸引人的) 春季版支持是“可刷新豆”功能。
可更新豆是一种动态语言支持的豆子。其中有少量 配置,一个动态语言支持的 BEAN 可以监控其底层的变化 当动态语言源文件 是 变更(例如,当你编辑并保存文件系统中的更改时)。
这允许你部署任意数量的动态语言源文件,作为 应用程序,配置Spring容器以创建由动态支持的豆子 语言源文件(使用本章所述的机制),以及(后来, 随着需求变化或其他外部因素的介入,编辑动态 语言源文件,并且他们所做的任何更改都反映在 bean 中。 由更改后的动态语言源文件支持。没有必要关闭 运行应用程序(或在网页应用的情况下重新部署)。这 动态语言支持的 Bean 如此修正后,从 更改了动态语言源文件。
| 该功能默认关闭。 |
现在我们可以看看一个例子,看看使用可更新软件有多容易
豆。要开启可更新豆子功能,必须精确指定一个
附加属性<语言/>你豆子定义中的元素。所以
如果我们坚持前面的例子
本章,以下示例展示了我们在春季XML中会更改的内容
实现可更新豆子的配置:
<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>
这真的就是你要做的全部。这刷新-检查-延迟定义在信使豆子定义是豆子在
对底层动态语言源文件所做的任何更改进行刷新。
你可以通过给刷新-检查-延迟属性。记住,默认刷新行为是
禁用。如果你不想要刷新行为,就不要定义属性。
如果我们运行以下应用程序,就可以运行可刷新功能。
(请原谅那些“跳过重重障碍暂停执行”的恶作剧
在下一段代码中。)这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());
}
}
那么,假设在本例中,所有调用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
}
}
程序运行时,输入暂停前的输出为我会跳弗鲁格舞.
在对源文件进行更改并保存后,程序恢复执行,
将getMessage()动态语言支持的方法信使实现为《我会跳弗鲁格》(注意包含
附加引号)。
如果脚本的更改发生在 的窗口内,则不会触发刷新
这刷新-检查-延迟价值。脚本的改动实际上要到后来才会被处理
在动态语言支持的 Bean 上调用一个方法。只有当一个方法是
调用一个动态语言支持的 Bean 来检查底层脚本是否
消息来源变了。任何与脚本刷新相关的例外情况(例如
遇到编译错误或发现脚本文件已被删除)
导致一个致命异常被传播到呼叫代码。
之前描述的可刷新豆子行为不适用于动态语言
源文件定义为<lang:inline-script/>元素符号(参见内联动态语言源文件)。此外,它仅适用于在
实际上可以检测到底层源文件的变更(例如,通过代码
它检查存在于
文件系统)。
内联动态语言源文件
动态语言支持还可以支持动态语言源文件,这些文件
直接嵌入春豆定义中。更具体地说,是<lang:inline-script/>element 让你可以立即定义动态语言源
在 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验证器实现到春季MVC中控制器.这只是片刻
使用内联源。(参见脚本验证器,了解此类
示例。)
在动态语言支持豆子的背景下理解构造子注入
关于斯普林的动态,有一件非常重要的事需要注意 语言支持。也就是说,你目前无法提供构造器参数 对动态语言支持的豆子(因此,构造子注入不适用于 动态语言支持的豆子)。为了使这种特殊处理方式 构造者和属性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>
实际上,这一限制并不像最初看起来那么严重,因为二传手 注入是绝大多数开发者偏爱的注射风格 (关于这是否是好事,我们留待以后讨论。)
3.2.2. 酷炫豆子
本节介绍如何使用Groovy in Spring中定义的豆子。
Groovy 主页包含以下描述:
“Groovy 是一种适用于 Java 2 平台的敏捷动态语言,具备许多 这些功能是人们非常喜欢的,比如Python、Ruby和Smalltalk等语言所创造的 这些内容通过类似Java的语法向Java开发者开放。”
如果你从头读过本章,你已经见过一个Groovy动态语言支持的例子 豆。现在再举一个例子(同样是Spring测试套件中的一个例子):
package org.springframework.scripting;
public interface Calculator {
int add(int x, int y);
}
以下示例实现了计算器Groovy 中的界面:
// from the file 'calculator.groovy'
package org.springframework.scripting.groovy
class GroovyCalculator implements Calculator {
int add(int x, int y) {
x + y
}
}
以下豆子定义使用了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 = ctx.getBean("calculator", Calculator.class);
System.out.println(calc.add(2, 8));
}
}
运行上述程序的结果输出(毫不意外)10.
(更多有趣的例子,请参见动态语言展示项目
复杂示例或见本章后面的示例场景)。
你不能在每个Groovy源文件上定义超过一个类。虽然这完全是 在Groovy中合法,这(可以说)是一种不好的做法。为了保持一致 Spring团队认为,你应该尊重标准的Java。 每个源文件的(公)类约定。
通过回调自定义 groovy 对象
这GroovyObjectCustomizer接口是一个回溯,让你挂钩更多
创建逻辑进入制作Groovy背板豆子的过程。例如
该接口的实现可以调用任何必要的初始化方法,
设置一些默认属性值,或者指定自定义元阶级.以下列表
显示GroovyObjectCustomizer界面定义:
public interface GroovyObjectCustomizer {
void customize(GroovyObject goo);
}
Spring Framework 实例化了你的 Groovy backed 豆子,然后
传递被创造的GroovyObject到指定的GroovyObjectCustomizer(如果只有一个
已被定义)。你可以随心所欲地使用提供的GroovyObject参考。我们预计大多数人都想设置自定义元阶级用这个
以下示例展示了如何回调:
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 中对元编程的完整讨论超出了春季的范围
参考手册。请参阅Groovy参考手册的相关部分,或者做一个
网上搜索。有很多文章讨论了这个话题。实际上,利用一个GroovyObjectCustomizer如果你使用Spring命名空间支持,就很简单,因为
以下示例展示了:
<!-- 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"/>
你也可以指定一个Groovy编译定制器(例如一个ImportCustomizer)
甚至是完整的Groovy编译器配置与斯普林的同一个位置的物体GroovyObjectCustomizer.此外,你可以设定一个共同点GroovyClassLoader有定制
在ConfigurableApplicationContext.setClassLoader水平;
这也导致了共享GroovyClassLoader使用情况,因此推荐用于
大量脚本豆(避免孤立)GroovyClassLoader每颗豆子的实例数)。 |
3.2.3. 豆壳豆
本节介绍春季如何使用BeanShell豆。
BeanShell主页包含以下内容 描述:
BeanShell is a small, free, embeddable Java source interpreter with dynamic language features, written in Java. BeanShell dynamically runs standard Java syntax and extends it with common scripting conveniences such as loose types, commands, and method closures like those in Perl and JavaScript.
与Groovy不同,BeanShell支持的豆子定义需要一些(较小的)额外要求
配置。Spring 中 BeanShell 动态语言支持的实现是
有趣的是,Spring 创建了一个 JDK 动态代理,实现了所有
这些接口在脚本接口属性值<lang:bsh>元素(这也是为什么你必须在值中至少提供一个接口的原因
属性的 ,因此,当你使用 BeanShell 支持时,编程到接口
Beans)。这意味着每个对 BeanShell 支持对象的方法调用都会经过
JDK 动态代理调用机制。
现在我们可以展示一个完整的示例,说明使用基于 BeanShell 的 bean,实现了
这信使本章早些时候定义的接口。我们再次展示
的定义信使接口:
package org.springframework.scripting;
public interface Messenger {
String getMessage();
}
以下示例展示了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>
参见“场景”,了解一些你可能想使用的场景 豆壳基豆。
3.3. 场景
在脚本语言中定义Spring管理豆子的可能情景包括 有益的选择多种多样。本节描述了两种可能的使用场景 春季支持动态语言。
3.3.1. 脚本化的春季MVC控制器
一类可以受益于动态语言支持豆子的类是 Spring MVC控制器的成员。在纯Spring MVC应用中,导航流 通过网页应用,在很大程度上是由封装其中的代码决定的 你的Spring MVC控制器。作为导航流程和其他展示层逻辑 或网页应用需要更新以应对支持问题或变化 业务需求,可能更容易通过以下方式实现所需的更改 编辑一个或多个动态语言源文件,并看到这些变化 会立即反映在运行中的应用程序状态中。
请记住,在诸如 春季,通常会让展示层非常薄,所有 应用包含在域和服务中的丰富业务逻辑 叠类。将Spring MVC控制器开发为动态语言支持的豆子,使得 你通过编辑和保存文本文件来更改演示图层的逻辑。任何 对这些动态语言源文件的更改取决于配置 这些数据会自动反映在由动态语言源文件支持的豆子中。
| 实现对动态语言支持的任何变更的自动“拾取” 豆子,你必须启用“可更新豆子”功能。有关此功能的完整介绍,请参见“可补充豆”条目。 |
以下示例展示了一个org.springframework.web.servlet.mvc.Controller实现
通过使用Groovy动态语言:
// 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 应用开发的另一个领域可能会受益于 动态语言支持的豆子所提供的灵活性是验证。可以 使用松散类型的动态语言更容易表达复杂的验证逻辑 (可能还支持内联正则表达式)与普通Java相对。
同样,开发验证器作为动态语言支持的豆子可以让你改变 通过编辑和保存简单的文本文件来验证逻辑。任何此类变化均为 (取决于配置)自动反映在执行 运行应用程序,不需要重启应用程序。
| 实现动态语言支持的自动“拾取”任何更改 豆子,你必须启用“可更新豆”功能。详见“可补充豆”一文,了解该功能的完整详细介绍。 |
以下示例展示了一个Springorg.springframework.validation.Validator通过使用 Groovy 动态语言实现(详见 Spring 验证器界面的验证,讨论验证器界面):
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 — 脚本Beans的咨询
你可以用Spring AOP框架来建议脚本豆。春季AOP Framework 实际上并不知道被建议的 bean 可能是脚本化的 Bean,也就是你所有AOP的使用场景和功能(你用过的,或者打算用的) 使用脚本豆。当你建议脚本豆时,不能使用基于类的 代理。你必须使用基于接口的代理。
你并不局限于为脚本豆提供建议。你也可以直接写化相 并用支持的动态语言来指导其他春季豆。 不过这确实是动态语言支持的高级应用。
3.4.2. 范围界定
如果不明显,脚本豆也可以像
其他任何豆子。这范围属性在各种<语言/>元素设
你控制底层脚本豆的范围,就像普通的
豆。(默认范围为单例,
就像“普通”豆子一样。)
以下示例使用了范围属性定义一个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 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="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. 该朗XML 模式
这朗Spring XML 配置中的元素处理的是暴露已被
用动态语言(如 Groovy 或 BeanShell)编写,作为 Spring 容器中的豆子。
这些元素(以及动态语言支持)在动态语言支持中得到了全面介绍。请参见该节
关于该支持的详细信息及朗元素。
要使用以下元素朗你需要在
在你的 Spring XML 配置文件顶部。以下摘录引用文本
正确的模式,使得标签中的朗命名空间可供您使用:
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">
<!-- bean definitions here -->
</beans>