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/