Categories: Spring Boot Kotlin
在写了很多年的Ruby / Rails之后,最近我发现自己写了大量的Spring Boot应用程序。 Spring Boot 是JVM的一个很好的框架,它通过“使创建“可以轻松运行”的独立的,生产级的基于Spring的应用程序变得容易”来关注开发人员的工作效率。它具有许多Rails的感觉。“常规配置”部门,但是由于我最终使用Java 8,因此我失去了用Ruby编写时获得的“乐趣”。尽管Java 8在Java 7方面进行了重大改进,但我还是想知道通过使用 Kotlin 编写Spring Boot应用程序 ,我可以获得更多的喜悦 。
Kotlin是 JetBrains的 一种新语言,它 是IntelliJ和RubyMine的创建者,可以代替Java开发其产品。 他们的目标是创建一种更加简洁的基于JVM的语言,该语言有助于提高开发人员的工作效率,避免Java开发中的一些常见陷阱并与现有Java程序100%兼容。 它以Java 6为基准,同时仍添加了一些出色的语言功能,因此对Android开发也非常有用。
这篇文章以及所有后续文章将以现有的Java 8 / Spring Boot 应用程序 作为探索的起点。 这将使我看到Java 8语法和Kotlin语法之间的直接比较。 这次旅行将使我能够亲身体验Spring Boot / Kotlin应用程序的外观,并在学习过程中学习比“ Hello World”应用程序还多的语言。 如果您想在旅途中继续前进,可以在 GitHub上查看 不断发展的源代码 。
此外,这些帖子并不意味着要成为有关Kotlin的完整教程,而仅涵盖与转换有关的语言功能。 如果您需要完整的教程,则Kotlin网站上有很多 很好的信息 。
最后,如果您 对代码 有任何改进建议 ,请随时提交GitHub问题或提交拉取请求。
启动Spring Boot应用程序时,我们需要的第一件事是应用程序类。 这是 我开始 的 应用程序类 :
package com.example.billing; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ...
这里没有惊喜。
main()
当您运行可执行jar文件时,我们在Spring Boot检测到的Application类上
创建一个静态
方法。
这是Kotlin中的同一应用程序类:
package com.example.billing import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker import org.springframework.cloud.client.discovery.EnableDiscoveryClient @SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker // This class must not be final or Spring Boot is not happy. open class Application { companion object { @JvmStatic fun main(args: Array<String>) { SpringApplication.run(Application::class.java, *args) } } }
您可能会注意到的第一个区别是缺少分号。 是的,女士们,先生们,在科特林没有分号。 尽管对某些人来说不是什么大不了的事,但这对我来说是朝正确方向迈出的一步。
我注意到的下一个区别是
open
类定义前面
的
关键字。
默认情况下,Kotlin中的类是最终类,这是根据
Effective Java :
继承的设计和文档中的
第17项,
否则将禁止使用
。
这是我第一次在Kotlin的“强制执行良好做法”与Spring Boot的约定之间产生摩擦。
该
@SpringBootApplication
是一个方便的注释,标记与类
@Configuration
,
@EnableAutoConfiguration
和
@ComponentScan
注释。
正是
@Configuration
注解强制使用
open
关键字。
open
在应用程序启动时,
删除
关键字会导致运行时错误:
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Configuration class 'Application' may not be final. Remove the final modifier to continue.
由于此应用程序类不包含任何配置信息,因此修复起来很容易。
而不是使用的
@SpringBootApplication
标注可以
替代 的
@EnableAutoConfiguration
和
@ComponentScan
注释。
package com.example.billing import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker import org.springframework.cloud.client.discovery.EnableDiscoveryClient @EnableAutoConfiguration @ComponentScan @EnableDiscoveryClient @EnableCircuitBreaker class Application { companion object { @JvmStatic fun main(args: Array<String>) { SpringApplication.run(Application::class.java, *args) } } }
我注意到的最终差异在于
main()
方法
的定义
。
Kotlin有一个
伴随对象
的想法
。
这些对象的使用方式类似于Java中的静态方法,但不完全相同。
这就是
@JvmStatic
注释的来源。该注释告诉Kotlin生成实际的Java静态方法,而不是Kotlin中默认的“ kinda,sorta”方法。
此注释是对JVM兼容性进行投资的一个很好的例子。
该
main()
方法也缺少
public
修饰符。
默认情况下
,
方法
在Kotlin中
是公共的
,这减少了Java应用程序中存在的样板。
最后,您会注意到Kotlin中的数组是实际的参数化类,而不是Java中的原始类型。 Kotlin还在变量定义后放置类型注释。 我们将在以后的帖子中探讨为什么这很重要。
Kotlin应用程序类的最后一个难题是,您必须告诉Spring Boot在哪里可以找到应用程序类。 在Gradle中,就像这样简单:
springBoot { mainClass = 'com.example.billing.Application' }
Kotlin还允许在类之外定义函数,因此我们可以将应用程序类重写为:
package com.example.billing import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker import org.springframework.cloud.client.discovery.EnableDiscoveryClient import org.springframework.context.annotation.ComponentScan @EnableAutoConfiguration @ComponentScan @EnableDiscoveryClient @EnableCircuitBreaker class Application fun main(args: Array<String>) { SpringApplication.run(Application::class.java, *args) }
如果执行此操作,则该
main()
方法在名为的类上定义,该类
ApplicationKt
以file命名
Application.kt
,这会稍微改变
build.gradle
条目:
springBoot {
mainClass = 'com.example.billing.ApplicationKt'
}
此定义
main()
稍微
简化了
方法
的签名
。
注释和显式的伴随对象已经一去不复返了,因此代码也变得更加整洁。
我不确定我更喜欢哪一个。
使用伴随对象可以更明确地说明哪个类包含该
main()
方法,但是上面的定义更为简洁。
在这里,我们以较少的代码交换来隐式理解编译器将生成ApplicationKt类。
随着时间的流逝,我认为简化的应用程序类将在我身上发展。
在我看来,Kotlin作为“更好的Java”朝正确的方向迈出了一步。 在我看来,语言设计师已尽其所能保持与现有Java程序的兼容性,同时又不受Java遗留的束缚。 缺少半冒号似乎是微不足道的,但会在大型代码库中加起来,并且在语言级别实施最佳实践也将有助于大型代码库。
是的,在平稳地与Spring Boot集成方面存在一些小难题,但是新语法和语言结构的好处远远超过了这些难题。
在我们 在下一篇文章中 ,我们将看看Java的 Spring Boot 配置类,并将它们与自己的兄弟科特林。 我希望我们将继续看到Kotlin带来的收益将超过Spring Boot带来的摩擦。
在 本系列 的 第一篇文章 中,我们研究了Spring Boot应用程序类从Java 8到Kotlin的转换。 这些迁移的好处是,由于Kotlin与旧版Java的结合很好,因此可以逐步完成它们。 实际上,这是该语言的设计考虑因素之一。
在第二篇文章中,我们将研究配置类到Kotlin的转换。
这是用Java 8编写的Spring Boot配置类的示例:
package com.example.billing; import com.example.billing.reocurringPayments.Service; import com.example.payments.RecurlyGateway; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration class Configuration { @Bean public com.example.payments.Gateway paymentGateway() { return new RecurlyGateway(); } @Bean public Service serviceThatMayFail() { return new Service(); } }
这是用Kotlin编写的同一配置类:
package com.example.billing import com.example.billing.reocurringPayments.Service import com.example.payments.RecurlyGateway import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration open class Configuration { @Bean open fun paymentGateway() = RecurlyGateway() @Bean open fun serviceThatMayFail() = Service() }
没有很多巨大的差异,但以下是一些对我来说比较小的差异:
必须将Configuration类声明为打开。 这是因为Spring Boot继承了您的配置类,但是Kotlin默认将它们定型为final。 有关 详情, 请参见 此处 。
出于与上述相同的原因,必须声明@Bean函数为打开状态。
函数上没有返回类型,因为Kotlin会推断这些类型。 这种类型推断是Kotlin我最喜欢的功能之一。
Kotlin对于
单表达式函数
具有隐式返回(且没有花括号)
。
当函数主体中只有一个表达式时,Kotlin会自动假定您要返回该值,因此不需要显式
return
或大括号。
对于具有多个表达式的主体,
return
仍然是必需的,因为编译器可能无法猜测函数的返回类型。
new
初始化对象时
没有
关键字。
再加上类型推断,隐式返回和单条语句/不使用大括号,就构成了一个不错的紧凑型配置类。
在Kotlin中,Spring配置类对我来说是个好主意。 实际的代码差异只有4行代码(18对14),但是在Kotlin中,视觉噪声大大降低了。 对于我来说,必须将类和所有方法都声明为开放似乎有点笨拙,但由于类型推断,单表达式函数没有返回值以及这些类从Kotlin获得的其他改进,我愿意忽略它。
谢谢阅读。 在我们 在下一篇文章中 ,我们将看看使用科特林的数据类实现的POJO。
欢迎使用我们的Java 8-> Kotlin转换的第三部分,用于Spring Boot应用程序。 上次 我们看到将配置类转换为Kotlin如何帮助清除Java中所需的一些样板代码。
在第三部分中,我们将继续我们的主题“用Kotlin编写更少的代码”,并研究 Kotlin数据类 如何 帮助我们清理POJO数据类。
我们的 起点 是一个普通的旧Java对象(POJO),用于保存一些通过RabbitMQ发送的数据:
package com.example.email; import java.io.Serializable; public class EmailMessage implements Serializable { private final String toAddress; private final String subject; private final String body; public EmailMessage(String toAddress, String subject, String body) { this.toAddress = toAddress; this.subject = subject; this.body = body; } public String getToAddress() { return toAddress; } public String getSubject() { return subject; } public String getBody() { return body; } }
下面是 同一类 的实现 科特林数据类 :
package com.example.email import java.io.Serializable data class EmailMessage(val toAddress: String, val subject: String, val body: String) : Serializable
该代码不仅比Java代码短得多,而且具有更多功能。 作为Kotlin数据类,此少量代码将获得:
生成一
equals()/hashCode()
对的
实现
默认
toString()
方法
一种
copy()
允许轻松更改对象的各个属性的方法
在赋值语句中 分解 对象 的能力 。
JSON反序列化 和 Spring Data JPA类 可以方便地使用此功能 。
当有人评估从Java到Kotlin的转换时,这些数据类是在Spring Boot应用程序中采用Kotlin的主要原因。 它们可以帮助您编写并维护更少的代码,而我想维护的代码也越少越好。
https://engineering.pivotal.io/post/spring-boot-application-with-kotlin/