资讯专栏INFORMATION COLUMN

Kotlin框架巡礼

_Suqin / 815人阅读

摘要:框架官方支持的框架,风格颇为类似,并且充分发挥了的强类型优势。这是一个主要面向的框架,为提供了一些额外特性。依赖注入框架用法简单,支持等特性。

首先要说明,Kotlin支持你所知道的所有Java框架和库,包括但不限于Spring全家桶、Guice、Hibernate、MyBatis、Jackson等,甚至有人在用Kotlin写Spark大数据程序,因此Kotlin不需要专门的框架。因此,为Kotlin开发框架的人,都是怀着满满的爱!

Kotlin现在主要流行于Android开发,我是搞后端开发的,不熟悉Android,就不妄言了。这篇文章主要介绍后端框架,包括Web、SQL、依赖注入、测试这些方面。

Web框架 Wasabi

- An HTTP Framework https://github.com/wasabifx/wasabi

极简的Web框架,基于Netty构建,编程风格效仿了Ruby的Sinatra和Node.js的Express。

Java也有个效仿Sinatra风格的Web框架,叫Spark(是的,与某大数据框架重名了)。

使用很简单:

var server = AppServer()

server.get("/", { response.send("Hello World!") })

server.start()

也可以这么写:

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"] } )

为了提供可维护性,可以在别处定义一个方法,在程序入口引用它:

appServer.get("/customer", ::getCustomer)

这种微框架很适合快速为一个后端服务添加REST接口

Kara

https://github.com/TinyMissio...

JetBrains官方支持的Web框架,特色是类型安全的HTML DSL和CSS DSL (风格类似Haml/Slim和SASS/LESS)

因为Kotlin是支持动态执行代码的,所以DSL理论上是可以热修改的,但是不知道Kara框架有没有内置这个特性。

DSL示例

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,还可以用面向对象或函数式的方式来复用。

Controllers 像Spring MVC和Django的风格

不需要用到反射,性能更高:

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"]}")
    })
}
当然也有拦截器,在这里叫Middleware

实现这个接口并绑定到路由路径就可以了:

/**
*    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重复了。

SQL框架 Exposed

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
    val name = varchar("name", length = 50) // Column
    val cityId = (integer("city_id") references Cities.id).nullable() // Column
}

object Cities : Table() {
    val id = integer("id").autoIncrement().primaryKey() // Column
    val name = varchar("name", 50) // Column
}

fun main(args: Array) {
    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去改?这方面需要提高。

Requery

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 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 getPhoneNumbers();

    String getEmail();
}

它提供了SQL DSL,看起来似乎依赖代码生成:

Result 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
}
Kwery, Kuery, Kotliquery

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。

依赖注入框架 Kodein

https://github.com/SalomonBry...

用法简单,支持scopes, modules, lazy等特性。

val kodein = Kodein {
    bind() with provider { RandomDice(0, 5) }
    bind() with singleton { SqliteDS.open("path/to/file") }
}

class Controller(private val kodein: Kodein) {
    private val ds: DataSource = kodein.instance()
}

个人认为Kodein没有什么亮点。

因为传统的Spring和Guice都是可以用的,功能和稳定性更有保证,所以建议继续用传统的吧。

测试框架 Spek

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.1、2.11.2,也不知道实际运行的是哪个。)

这些新兴的Kotlin框架延续了Kotlin的简洁和强大,相信它们很快就会展现出光明的前途!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66645.html

相关文章

  • 毫无色彩的二哲和他的巡礼之年

    摘要:前戏今年,对于我个人而言遭遇了三个重大的转折点。尽可能的把沟通成本用约定和文档降低。学习的这一年可以说年的学习,在上半年的精力,放在了技术上。而下半年则相反。 前戏 今年,对于我个人而言遭遇了三个重大的转折点。 15年9月大三休学创业,16年9月重新复学大三 在2016年4月顺利引进天使轮,公司从厦门集美区搬到了深圳南山区 16年底,我们正在准备接入A轮 16年与15年相比,总体来...

    Alex 评论0 收藏0
  • 初探Kotlin+SpringBoot联合编程

    摘要:是一门最近比较流行的静态类型编程语言,而且和一样同属系。这个生成的构造函数是合成的,因此不能从或中直接调用,但可以使用反射调用。 showImg(https://segmentfault.com/img/remote/1460000012958496); Kotlin是一门最近比较流行的静态类型编程语言,而且和Groovy、Scala一样同属Java系。Kotlin具有的很多静态语言...

    xiaokai 评论0 收藏0
  • 2016年前端盘点合集

    摘要:年已经过去,这一年前端领域发生了什么有哪些技术和项目引人注目工程师们观点和看法又有怎样的变化在此,整理了一些对过去的年盘点的资料,一是希望能借此提高自己的姿势水平,二是希望能为年的学习有所指导。 2016年已经过去,这一年前端领域发生了什么?有哪些技术和项目引人注目?工程师们观点和看法又有怎样的变化?在此,整理了一些对过去的2016年盘点的资料,一是希望能借此提高自己的姿势水平,二是希...

    aisuhua 评论0 收藏0
  • Kotlin + Spring Boot服务端开发

    摘要:是什么著名厂商开发的基于的静态类型编程语言,声称。语法近似和,且已活跃在开发领域,被誉为平台的。各有千秋,我更认同改写字节码。的作用是防止敏感字段被泄露到中,的作用是软删除数据不可见,但没有真的删除。 Kotlin是什么? 著名IDE厂商JetBrains开发的基于JVM的静态类型编程语言,声称100% interoperable with Java。Kotlin是由工程师设计的,各种...

    e10101 评论0 收藏0

发表评论

0条评论

_Suqin

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<