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-safe呼叫)
// ?: (用預設值給null兜底)
val numStr = getNumberOrNull()?.toString() ?: ""
// 自動關閉的資源
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() }
}
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}"
}
先寫一個主類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)
}
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)"
}
好簡單啊!現在,在IDE中執行Application.kt檔案,就開始執行了!用瀏覽器開啟http://localhost:8080/api/users/1
現在要把資料儲存到資料庫了:
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
我們對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)
}
然後要修改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)
}
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>()
}
第一個類是所有實體模型的基類,提供一些通用欄位。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(name, password).apply {
save()
}
}
}
get方法真正向資料庫做查詢了!增加了create方法來建立使用者!如果想用瀏覽器快速測試,把RequestMethod.POST改成GET,輸入連結http://localhost:8080/api/users/new?name=admin&password=123 試試!
一個注意事項
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 &
我自己用的命令不一樣:
unzip -q myapp.jar
nohup java -cp `.:./lib/*` com.myapp.ApplicationKt &
注意當前所在的工作目錄,日誌目錄/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構建的!