Kotlin + Spring Boot服务端开发

开发 后端
Kotlin能与Java混合使用,并且直接复用Java的生态系统(库、框架、工具)。一个已有的Java项目,只需引用Kotlin的Maven/Gradle插件,以及引用Kotlin标准库的依赖,就可以逐渐掺入Kotlin代码。你完全可以当它是a better Java。Spring Boot是流行的Web快速开发框架,使基于Spring的开发更便捷。

[[175220]]

Kotlin是什么?

著名IDE厂商JetBrains开发的基于JVM的静态类型编程语言,声称100% interoperable with Java。Kotlin是由工程师设计的,各种细节设计非常切合工程师的需要。语法近似Java和Scala,且已活跃在Android开发领域,被誉为Android平台的Swift。

Kotlin能与Java混合使用,并且直接复用Java的生态系统(库、框架、工具)。一个已有的Java项目,只需引用Kotlin的Maven/Gradle插件,以及引用Kotlin标准库的依赖,就可以逐渐掺入Kotlin代码。你完全可以当它是a better Java。

Kotlin的学习曲线极其平缓,学习量相当于一个框架。有经验的程序员阅读了文档就能立刻用起来了。不信你看:

举几个例子来说明Kotlin的优点吧,上代码:

//句尾不用写分号 
 
// 自动推导变量类型,无需声明 
val a = "Hello" 
 
// 简单的println 
println(a.length() == 5) 
 
// 不用写new, 直接调构造函数 
val b = String("Hello"
 
// 字符串插值 
"$a $b" == "Hello Hello" 
 
// if-else是表达式, 真方便! 
// ==相当于equals, 再也不怕忘写equals了! 
val oneOrTwo = if (a == "Hello") 1 else 2 
 
// ===相当于Java的== 
(a === b) == false 
 
// Lambda用{}包起来,若有唯一参数,参数名默认为it 
// 集合的函数式操作, 无需Java 8繁琐的stream.collect(Collectors.toList()) 
listOf(-1, 0, 1).map{it + 1}.filter{it > 0}) == listOf(1, 2) 
 
// 用一个默认值给null兜底 
val number = getNumberOrNull() ?: 0 
 
// 自动关闭的资源 
FileInputStream("MyFile").use { stream -> // 可指定参数名为stream, 取代默认的it 
  val firstByte = stream.read() 

 
// 可以更简单,一行 
val fileContent = File("MyFile").readText() 
 
// lazy, 延迟初始化 
class CPU { 
  val cpuCores by lazy { Runtime.getRuntime().availableProcessors() } 
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.

Kotlin为厌烦Java而疑虑Scala的人提供了避风港,为喜欢Groovy而想要静态类型的人提供了避风港。啊!生活。

Spring Boot是什么?

Spring Boot是流行的Web快速开发框架,使基于Spring的开发更便捷。我们已经知道Spring很好用,而Spring Boot的设计目标是:

  • 为一切Spring开发提供极速、通用的上手体验
  • 开箱即用,但是当默认值不适合需求时不会妨碍你做改变
  • 提供一组适用于各种项目类型的非功能性特性(如内嵌服务器、安全、度量、健康检查、外部配置)
  • 完全不需要代码生成和XML配置

Kotlin + Spring Boot

Kotlin能轻松集成Spring Boot,用Java怎么写,用Kotlin基本上也怎么写。

Spring能在线生成项目,免去创建项目的烦恼,请猛击链接http://start.spring.io/ 。

我们用Gradle构建,写一个build.gradle文件:

buildscript { 
  ext { 
    springBootVersion = '1.3.5.RELEASE' 
    kotlinVersion = '1.0.4' 
  } 
  repositories { 
    mavenCentral() 
  } 
  dependencies { 
    classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
  } 

 
apply plugin: 'kotlin' 
apply plugin: 'spring-boot' 
 
jar { 
  baseName = 'myapp' 
  version = '0.1-SNAPSHOT' 

sourceCompatibility = 1.8 
targetCompatibility = 1.8 
// class文件保留参数名称 
compileJava.options.compilerArgs.add '-parameters' 
compileTestJava.options.compilerArgs.add '-parameters' 
springBoot { 
  mainClass = 'myapp.ApplicationKt' 

 
dependencies { 
  compile 'org.springframework.boot:spring-boot-starter-aop' 
  compile 'org.springframework.boot:spring-boot-starter-web' 
  compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}" 
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.

先写一个主类Application.kt,放在src/main/kotlin目录下(自己想一个包名哈),来启动整个应用:

@SpringBootApplication 
open class Application { 
 
  @Bean 
  open fun json(): MappingJackson2JsonView { 
    return MappingJackson2JsonView(ObjectMapper()) 
  } 

 
fun main(args: Array<String>) { 
  SpringApplication.run(Application::class.java, *args) 
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

Kotlin的函数可定义在类外面,而特殊的main函数要么放在外面,要么放在伴生对象(companion object)里面。这里就放在外面吧!

你会发现class和fun前面有open修饰符,它的意思是非final,Kotlin默认一切都是final的,如果不想要final救要加上open。由于Spring有时要创建代理,要求类和方法不能为final,因此我们每一处都写上open,以免忘记。

这里只有一个json()方法,用来在Spring中初始化Jackson,这样我们就能使用JSON了。

现在来写一个RestController,提供RESTful API吧:

@RestController 
@RequestMapping("/api/users"
open class UserApi { 
    @RequestMapping("/{id}",  method = arrayOf(RequestMethod.GET)) 
      open fun get(@PathVariable id: Long) = "User(id=$id, name=admin, password=123)" 
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

好简单啊!现在,在IDE中运行Application.kt文件,就开始运行了!用浏览器打开http://localhost:8080/api/use...

现在要把数据保存到数据库了:

Spring Boot使用JPA非常简单(照着官网的getting started学吧),但我要介绍另一种ORM框架——Ebean,它模仿了Rails的Active Record,支持常用的JPA注解。值得一提的是,Ebean的作者也喜欢Kotlin。

需要一个配置文件src/main/resources/ebean.properties :

# 是否生成建表SQL 
ebean.db.ddl.generate=true 
# 是否执行建表SQL 
ebean.db.ddl.run=false 
 
datasource.db.username=DB用户名 
datasource.db.password=DB密码 
datasource.db.databaseUrl=jdbc:mysql://localhost:3306/你的database名称 
datasource.db.databaseDriver=com.mysql.jdbc.Driver  
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

我们对ebean.db.ddl.run(是否执行建表SQL)选择了false。因为Ebean会生成建表SQL,我们可以手动执行,避免每次都重新建表,把数据丢弃了。编写实体类后再运行,SQL会生成在项目目录下,手动执行一下吧!(亦可在***启动前把ebean.db.ddl.run改成true)

然后在Spring中初始化Ebean吧:

// 把这个方法添加到Application类 
  @Bean(autowire = Autowire.BY_TYPE) 
  open fun getEbeanServer(): EbeanServer { 
    val config = ServerConfig() 
    config.name = "db" 
    config.loadFromProperties() 
    config.isDefaultServer = true 
    return EbeanServerFactory.create(config) 
  }  
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

然后要修改main方法,在Spring之前先执行Ebean的agent,改写实体类的字节码:

fun main(args: Array<String>) { 
  val packageName = "com.iostate.**" // 改成你自己的包名,实体类要放在这个包里面 
  if (!AgentLoader.loadAgentFromClasspath("avaje-ebeanorm-agent"
                                          "debug=1;packages=$packageName")) { 
    System.err.println( 
      "avaje-ebeanorm-agent not found in classpath - not dynamically loaded"
  } 
  SpringApplication.run(Application::class.java, *args) 
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

Ebean需要执行agent来改写字节码(instrumenation),而Hibernate则选择了给实体对象创建动态代理(dynamic proxy),都是为了能对实体进行AOP操作。

instrumenation使用复杂,调试简单;dynamic proxy使用简单,调试复杂。各有千秋,我更认同改写字节码。

编写实体类:

import javax.persistence.* 
 
import com.avaje.ebean.Model 
import com.avaje.ebean.annotation.WhenCreated 
import com.avaje.ebean.annotation.WhenModified 
import java.sql.Timestamp 
 
import com.avaje.ebean.annotation.SoftDelete 
import com.fasterxml.jackson.annotation.JsonIgnore 
 
@MappedSuperclass 
abstract class BaseModel : Model() { 
 
  @Id @GeneratedValue 
  var id: Long = 0 
 
  @Version 
  var version: Long = 0 
 
  @WhenCreated 
  var whenCreated: Timestamp? = null 
 
  @WhenModified 
  var whenModified: Timestamp? = null 
 

 
@Entity 
class User ( 
    var name: String = ""
    @JsonIgnore 
    var password: String = "" 
    @SoftDelete 
    var deleted: Boolean = false 
) : BaseModel() { 
  companion object find : Find<Long, User>() 
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

***个类是所有实体模型的基类,提供一些通用字段。id是自增主键,version是乐观锁的标志,whenCreated是创建时间,whenModified是修改时间。有的变量类型以问号结尾,这个跟Swift语言是一样的,表示可为null(默认是非null的)。

第二类是User,行数很少,没有繁琐的getter/setter。@JsonIgnore的作用是防止敏感字段被泄露到JSON中,@SoftDelete的作用是软删除(数据不可见,但没有真的删除)。companion object find : Find<Long, User>()提供了一组快捷查询方法,如byId(id)all() 。

现在把UserApi修改如下:

@RestController 
@RequestMapping("/api/users"
open class UserApi { 
  @RequestMapping("/{id}",  method = arrayOf(RequestMethod.GET)) 
  open fun get(@PathVariable id: Long) = User.byId(id) 
 
  @RequestMapping("/new", method = arrayOf(RequestMethod.POST)) 
  open fun create(@RequestParam name: String, @RequestParam password: String): User { 
    return User(namepassword).apply { 
      save() 
    } 
  } 
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

get方法真正向数据库做查询了!增加了create方法来创建用户!如果想用浏览器快速测试,把RequestMethod.POST改成GET,输入链接http://localhost:8080/api/use... 试试!

一个注意事项

Spring Boot能把程序打包成jar直接运行,这是很方便群众的!但是JSP和Ebean在jar模式都无法工作。

那么在生产环境要怎么解决呢?可以把jar解压运行!

参考文档的exploded archives: http://docs.spring.io/spring-...

# 解压 
unzip -q myapp.jar 
# 运行 
java org.springframework.boot.loader.JarLauncher 
# 生产模式用以下的nohup方式,以防程序随着shell一起关闭 
nohup java org.springframework.boot.loader.JarLauncher &  
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

我自己用的命令不一样:

unzip -q myapp.jar 
nohup java -cp '.:./lib/*' com.myapp.ApplicationKt &  
  • 1.
  • 2.

注意当前所在的工作目录,日志目录/logs会创建在当前工作目录下。

收工

我提供了一个示例项目,比较粗糙,请多多包涵 https://github.com/sorra/bms

老外也有几个示例项目,可供参考:

Spring Boot Kotlin project with a REST Webservice and Spring Data: https://github.com/sdeleuze/s...

Demo Webapp using SpringBoot, Kotlin and React.js: https://github.com/winterbe/s...

顺带一提,轻境界就是用Kotlin + Spring Boot构建的!

责任编辑:庞桂玉 来源: segmentfault
相关推荐

2013-03-25 10:08:44

PHPWeb

2016-08-04 14:41:21

架构java服务端开发

2016-03-18 09:04:42

swift服务端

2012-03-02 10:38:33

MySQL

2010-08-03 09:59:30

NFS服务

2015-11-09 17:51:12

服务器端开发

2021-05-25 08:20:37

编程技能开发

2010-03-18 18:09:36

Java Socket

2023-08-08 08:17:23

VasDolly服务端参数

2021-04-26 13:20:06

Vue服务端渲染前端

2010-03-19 18:17:17

Java Server

2009-08-21 15:22:56

端口侦听

2010-02-24 15:42:03

WCF服务端安全

2022-12-29 08:56:30

监控服务平台

2009-08-21 15:59:22

服务端与客户端通信

2011-09-09 09:44:23

WCF

2009-08-21 16:14:52

服务端与客户端通信

2018-05-04 15:27:22

Spring Boo Web开发

2022-05-18 08:32:05

服务监控Prometheus开源

2024-03-06 14:58:52

客户端微服务架构
点赞
收藏

51CTO技术栈公众号