首先要说明,Kotlin支持你所知道的全部Java框架和库,包括但不限于Spring全家桶、Guice、Hibernate、MyBatis、Jackson等,甚至有人在用Kotlin写Spark大数据程序,所以Kotlin不须要专门的框架。所以,为Kotlin开发框架的人,都是怀着满满的爱!java
Kotlin如今主要流行于Android开发,我是搞后端开发的,不熟悉Android,就不妄言了。这篇文章主要介绍后端框架,包括Web、SQL、依赖注入、测试这些方面。git
- An HTTP Framework https://github.com/wasabifx/wasabigithub
极简的Web框架,基于Netty构建,编程风格效仿了Ruby的Sinatra和Node.js的Express。web
Java也有个效仿Sinatra风格的Web框架,叫Spark(是的,与某大数据框架重名了)。sql
使用很简单:编程
var server = AppServer() server.get("/", { response.send("Hello World!") }) server.start()
也能够这么写:json
server.get(“/“) { response.send("Hello World!") }
加一个前置拦截器(next()
表示进入下一步处理):后端
server.get("/", { val log = Log() log.info("URI requested is ${request.uri}") next() }, { response.send("Hello World!", "application/json") } )
获取参数:安全
server.get("/customer/:id", { val customerId = request.routeParams["id"] } )
server.get("/customer", { val customerName = request.queryParams["name"] } )
为了提供可维护性,能够在别处定义一个方法,在程序入口引用它:app
appServer.get("/customer", ::getCustomer)
这种微框架很适合快速为一个后端服务添加REST接口。
https://github.com/TinyMissio...
JetBrains官方支持的Web框架,特点是类型安全的HTML DSL和CSS DSL (风格相似Haml/Slim和SASS/LESS)
由于Kotlin是支持动态执行代码的,因此DSL理论上是能够热修改的,可是不知道Kara框架有没有内置这个特性。
HTML View:
class Index() : HtmlView() { override fun render(context: ActionContext) { h2("Welcome to Kara") p("Your app is up and running, now it's time to make something!") p("Start by editing this file here: src/com/karaexample/views/home/Index.kt") } }
HTML Layout:
class DefaultLayout() : HtmlLayout() { override fun render(context: ActionContext, mainView: HtmlView) { head { title("Kara Demo Title") stylesheet(DefaultStyles()) } body { h1("Kara Demo Site") div(id="main") { renderView(context, mainView) } a(text="Kara is developed by Tiny Mission", href="http://tinymission.com") } } }
Forms:
class BookForm(val book : Book) : HtmlView() { override fun render(context: ActionContext) { h2("Book Form") formFor(book, "/updatebook", FormMethod.Post) { p { labelFor("title") textFieldFor("title") } p { labelFor("isPublished", "Is Published?") checkBoxFor("isPublished") } } } }
CSS:
class DefaultStyles() : Stylesheet() { override fun render() { s("body") { backgroundColor = c("#f0f0f0") } s("#main") { width = 85.percent backgroundColor = c("#fff") margin = box(0.px, auto) padding = box(1.em) border = "1px solid #ccc" borderRadius = 5.px } s("input[type=text], textarea") { padding = box(4.px) width = 300.px } s("textarea") { height = 80.px } s("table.fields") { s("td") { padding = box(6.px, 3.px) } s("td.label") { textAlign = TextAlign.right } s("td.label.top") { verticalAlign = VerticalAlign.top } } } }
其实就是在写Kotlin代码,显然你能够自行扩展出更多的DSL,还能够用面向对象或函数式的方式来复用。
不须要用到反射,性能更高:
object Home { val layout = DefaultLayout() Get("/") class Index() : Request({ karademo.views.home.Index() }) Get("/test") class Test() : Request({ TextResult("This is a test action, yo") }) Post("/updatebook") class Update() : Request({ redirect("/forms") }) Get("/complex/*/list/:id") Complex(id : Int) : Request({ TextResult("complex: ${params[0]} id = ${params["id"]}") }) }
实现这个接口并绑定到路由路径就能够了:
/** * Base class for Kara middleware. * Middleware is code that is injected inside the request pipeline, * either before or after a request is handled by the application. */ abstract class Middleware() { /** * Gets called before the application is allowed to handle the request. * Return false to stop the request pipeline from executing anything else. */ abstract fun beforeRequest(context : ActionContext) : Boolean /** * Gets called after the application is allowed to handle the request. * Return false to stop the request pipeline from executing anything else. */ abstract fun afterRequest(context : ActionContext) : Boolean }
appConfig.middleware.add(MyMiddleware(), "/books")
综合来讲,Kara确实是比较优秀的web框架。
但有一点很差,默认使用3000端口,与Rails重复了。
https://github.com/jetbrains/...
JetBrains官方支持的SQL/ORM框架,风格颇为相似Django ORM,而且充分发挥了Kotlin的强类型优点。
项目主页有很长一段示例代码,全程都是强类型,很是流畅,使人赏心悦目:
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.SchemaUtils.create import org.jetbrains.exposed.sql.SchemaUtils.drop object Users : Table() { val id = varchar("id", 10).primaryKey() // Column<String> val name = varchar("name", length = 50) // Column<String> val cityId = (integer("city_id") references Cities.id).nullable() // Column<Int?> } object Cities : Table() { val id = integer("id").autoIncrement().primaryKey() // Column<Int> val name = varchar("name", 50) // Column<String> } fun main(args: Array<String>) { Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver") transaction { create (Cities, Users) val saintPetersburgId = Cities.insert { it[name] = "St. Petersburg" } get Cities.id val munichId = Cities.insert { it[name] = "Munich" } get Cities.id Cities.insert { it[name] = "Prague" } Users.insert { it[id] = "andrey" it[name] = "Andrey" it[cityId] = saintPetersburgId } Users.insert { it[id] = "sergey" it[name] = "Sergey" it[cityId] = munichId } Users.insert { it[id] = "eugene" it[name] = "Eugene" it[cityId] = munichId } Users.insert { it[id] = "alex" it[name] = "Alex" it[cityId] = null } Users.insert { it[id] = "smth" it[name] = "Something" it[cityId] = null } Users.update({Users.id eq "alex"}) { it[name] = "Alexey" } Users.deleteWhere{Users.name like "%thing"} println("All cities:") for (city in Cities.selectAll()) { println("${city[Cities.id]}: ${city[Cities.name]}") } println("Manual join:") (Users innerJoin Cities).slice(Users.name, Cities.name). select {(Users.id.eq("andrey") or Users.name.eq("Sergey")) and Users.id.eq("sergey") and Users.cityId.eq(Cities.id)}.forEach { println("${it[Users.name]} lives in ${it[Cities.name]}") } println("Join with foreign key:") (Users innerJoin Cities).slice(Users.name, Users.cityId, Cities.name). select {Cities.name.eq("St. Petersburg") or Users.cityId.isNull()}.forEach { if (it[Users.cityId] != null) { println("${it[Users.name]} lives in ${it[Cities.name]}") } else { println("${it[Users.name]} lives nowhere") } } println("Functions and group by:") ((Cities innerJoin Users).slice(Cities.name, Users.id.count()).selectAll().groupBy(Cities.name)).forEach { val cityName = it[Cities.name] val userCount = it[Users.id.count()] if (userCount > 0) { println("$userCount user(s) live(s) in $cityName") } else { println("Nobody lives in $cityName") } } drop (Users, Cities) } }
CRUD和各类查询都很容易表达,也能自动建表,确实方便得很。
可是文档没提到schema migration,想必是没这个功能。若是你想修改表结构,还得手动用SQL去改?这方面须要提升。
https://github.com/requery/re...
这是一个主要面向Android的Java ORM框架,为Kotlin提供了一些额外特性。
你须要把实体声明为abstract class或interface,而后标上相似JPA的注解:
@Entity abstract class AbstractPerson { @Key @Generated int id; @Index("name_index") // table specification String name; @OneToMany // relationships 1:1, 1:many, many to many Set<Phone> phoneNumbers; @Converter(EmailToStringConverter.class) // custom type conversion Email email; @PostLoad // lifecycle callbacks void afterLoad() { updatePeopleList(); } // getter, setters, equals & hashCode automatically generated into Person.java }
@Entity public interface Person { @Key @Generated int getId(); String getName(); @OneToMany Set<Phone> getPhoneNumbers(); String getEmail(); }
它提供了SQL DSL,看起来彷佛依赖代码生成:
Result<Person> query = data .select(Person.class) .where(Person.NAME.lower().like("b%")).and(Person.AGE.gt(20)) .orderBy(Person.AGE.desc()) .limit(5) .get();
用Kotlin能够写得更简洁:
data { val result = select(Person::class) where (Person::age gt 21) and (Person::name eq "Bob") limit 10 }
https://github.com/andrewoma/...
https://github.com/x2bool/kuery
https://github.com/seratch/ko...
这三个其实是SQL库,对JDBC作了一些封装,提供了简易的SQL DSL。我不想用篇幅来介绍,有兴趣的朋友能够去项目主页看一看。
还要特别推荐Ebean ORM框架 https://ebean-orm.github.io/ 融合了JPA和Active Record的风格,成熟度相对高一些,已有必定规模的用户群,虽然不是专为Kotlin设计,但做者也在使用Kotlin。
https://github.com/SalomonBry...
用法简单,支持scopes, modules, lazy等特性。
val kodein = Kodein { bind<Dice>() with provider { RandomDice(0, 5) } bind<DataSource>() with singleton { SqliteDS.open("path/to/file") } } class Controller(private val kodein: Kodein) { private val ds: DataSource = kodein.instance() }
我的认为Kodein没有什么亮点。
由于传统的Spring和Guice都是能够用的,功能和稳定性更有保证,因此建议继续用传统的吧。
https://github.com/JetBrains/...
JetBrains官方支持的规格测试框架,效仿了Ruby的RSpec,代码风格很类似,可读性很好:
class SimpleTest : Spek({ describe("a calculator") { val calculator = SampleCalculator() it("should return the result of adding the first number to the second number") { val sum = calculator.sum(2, 4) assertEquals(6, sum) } it("should return the result of subtracting the second number from the first number") { val subtract = calculator.subtract(4, 2) assertEquals(2, subtract) } } })
除此以外,Java的JUnit, TestNG, Mockito等框架在Kotlin中都是可使用的。
相比于Scala,Kotlin在实用性方面下了大功夫,无缝兼容Java,融入Java生态,自身也有简洁的语法和强大的DSL能力。(想想Scala 2.10 2.11 2.12的互不兼容,以及build一个项目会下载标准库的n个版本,例如2.11.0、2.11.一、2.11.2,也不知道实际运行的是哪一个。)
这些新兴的Kotlin框架延续了Kotlin的简洁和强大,相信它们很快就会展示出光明的前途!