为什么写此文

Kotlin很烦,Gralde很烦,还都是升级狂,加一块更烦。几个月不接触Kotlin,再次上手时便一片迷茫。所以记录此文,以便再次上手时查阅。

使用Gradle创建Kotlin项目

  1. mkdir hellokt 创建项目文件夹
  2. cd hellokt 切换到项目根目录
  3. gradle init --type java-application 使用Gradle初始化Java项目
  4. rm -rf src/main/java src/test/java gradle gradlew gradlew.bat 删除Java目录和GradleWrapper配置
  5. vim build.gradle 编辑Gradle项目配置
  6. mkdir -p src/main/kotlin src/test/kotlin 创建Kotlin目录
  7. vim src/main/kotlin/App.kt 编写Kotlin版HelloWorld
  8. gradle clean build run 使用Gradle清理、构建、运行,直接运行也可
  9. idea . 用IntelliJ IDEA 打开项目,所有选项均选择默认,开始用IDE进行开发

为什么要用命令行创建项目?

用图形化界面创建项目变量太多,人品不好容易掉坑里。用命令行创建项目,可以明确每个文件、每行代码的用途,整个过程可重现、可控制,还可以避免在IDE里某个步骤卡死半天没反应又结束不掉的尴尬。

为什么要删除GradleWrapper

  • 很烦、很烦、很烦 我想安静一会儿
  • 很大、很大、很大 我硬盘不够你折腾
  • 很慢、很慢、很慢 我知道有堵墙,不用你三天两头提醒

我不Care你的Gradle版本。编译不过我自然会升级Gradle构建脚本

build.gradle

// 注意,这个文件是Gradle构建脚本,是脚本,里面的代码是先后执行的。至少`buildscript`要放在`apply plugin`的前面。
// 构建脚本
buildscript {
    // 插件依赖
    dependencies {
        // Kotlin插件对应的包
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.10"
    }

    // 插件仓库。墙外人可直接用`mavencentral`、`jcenter`
    repositories {
        // 阿里的Maven中心仓库镜像
        maven { url "https://maven.aliyun.com/repository/central"  }
        // 阿里的jCenter镜像
        maven { url "https://maven.aliyun.com/repository/jcenter"  }
    }
}

// 此插件添加了 `gradle run` 命令,通过Gradle运行项目
apply plugin: 'application'
// 此插件对Kotlin语言提供了支持,可以编译Kotlin文件
apply plugin: 'kotlin'

// application插件run的入口class
mainClassName = 'App'

// 项目依赖
dependencies {
    // Kotlin分为两部分,语言部分和库部分。kotlin插件对语言部分提供支持,`kotlin-stdlib`对库部分提供支持。哪怕HelloWorld中使用的`println`也在库中。所以是Kotlin项目的必选依赖
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}

// 项目仓库
repositories {
    // Maven中心仓库墙内版
    maven { url "https://maven.aliyun.com/repository/central"  }
    // jCenter中心仓库墙内版
    maven { url "https://maven.aliyun.com/repository/jcenter"  }
}

App.kt

class App {
    companion object {
        @JvmStatic
            fun main(args: Array<String>) {
                println("hello kt")
            }
    }
}

之所以将代码放到类里头,是为了支持application插件,他需要指定一个含有JVM入口静态main方法的入口类。

也可以用带main函数的app.kt,此时mainClassName应配置为"AppKt"

用Gradle构建Kotlin版的SpringBoot应用

build.gradle

buildscript {
    repositories {
        maven { url "https://maven.aliyun.com/repository/central"  }
        maven { url "https://maven.aliyun.com/repository/jcenter"  }
    }
    dependencies {
        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.10'
        classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.0.3.RELEASE'
    }
}
apply plugin: 'application'
apply plugin: 'kotlin'

// SpringBoot插件。Kotlin默认一切final,Spring又需要各种代理,所以需要特殊处理。同时提供`spring:bootRun`命令
apply plugin: 'org.springframework.boot'
// Spring依赖管理。自动选择依赖版本。Gradle中没有Maven那样内建的依赖管理(通过Parent POM 实现),需要插件处理。
apply plugin: 'io.spring.dependency-management'

mainClassName = 'bj.App'

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib" // Kotlin是要在JVM里跑的。那么多语言特性,没有依赖库怎么跑
    compile "org.jetbrains.kotlin:kotlin-reflect" // 无反射不Spring。反射不在Kotlin标准库,需单独添加
    compile 'org.springframework.boot:spring-boot-starter' // 创建单机应用所需要的最基本的Starter
}
repositories {
    maven { url "https://maven.aliyun.com/repository/central"  }
    maven { url "https://maven.aliyun.com/repository/jcenter"  }
}

bj/App.kt

package bj
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.context.ApplicationListener
/**
 * Created by BaiJiFeiLong@gmail.com at 18-6-27 下午10:08
 */
@SpringBootApplication
open class App : ApplicationListener<ApplicationReadyEvent> {
    override fun onApplicationEvent(event: ApplicationReadyEvent?) {
        println("Ready.")
    }
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            SpringApplication.run(App::class.java, *args);
        }
    }
}

注意:

主类一定要放在包里头(不能用root或者说default),否则报java.lang.ClassNotFoundException: org.springframework.dao.DataAccessException

创建Ktor应用

build.gradle

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.10"
    }
    repositories {
        maven { url "https://maven.aliyun.com/repository/central"  }
        maven { url "https://maven.aliyun.com/repository/jcenter"  }
    }
}

apply plugin: 'application'
apply plugin: 'kotlin'

mainClassName = 'App'

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    // 2. 添加Ktor依赖
    compile "io.ktor:ktor-server-netty:1.0.0-beta-3"
    // 3.  添加Logback依赖。Ktor只依赖了Slf4J,没有Slf4J的具体实现。如果不导入一个Slf4J的实现,将打印不出日志来
    compile "ch.qos.logback:logback-classic:1.2.3"
}

repositories {
    maven { url "https://maven.aliyun.com/repository/central"  }
    maven { url "https://maven.aliyun.com/repository/jcenter"  }
    // 1. 添加Ktor仓库。没出正式版,所以Maven中心仓没有最新版本
    maven { url "https://dl.bintray.com/kotlin/ktor" }
}

bj/App.kt

import io.ktor.application.call
import io.ktor.http.ContentType
import io.ktor.response.respondText
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty

class App {
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            val server = embeddedServer(Netty, port = 8080) {
                routing {
                    get("/") {
                        call.respondText("xx", ContentType.Text.Plain)
                    }
                }
            }
            server.start(wait = true)
        }
    }
}

Ktor应用打包

build.gradle

gradle build 默认打包的jar不带Manifest,也不是FatJar,不能直接运行。添加shadow插件后,将多打包出一个可以直接运行的FatJar

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.10"
        // 1. 添加shadow插件的依赖
        classpath "com.github.jengelman.gradle.plugins:shadow:4.0.2"
    }
    repositories {
        maven { url "https://maven.aliyun.com/repository/central"  }
        maven { url "https://maven.aliyun.com/repository/jcenter"  }
    }
}

apply plugin: 'application'
apply plugin: 'kotlin'

// 2. 应用shadow插件
apply plugin: 'com.github.johnrengelman.shadow'

// 需要带main函数的kotlin文件main.kt或Main.kt
mainClassName = 'MainKt'

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    compile "io.ktor:ktor-server-netty:1.0.0-beta-3"
    // 用于组装HTML。非必选依赖
    compile "io.ktor:ktor-html-builder:1.0.0-beta-3"
    compile "ch.qos.logback:logback-classic:1.2.3"
}

repositories {
    maven { url "https://maven.aliyun.com/repository/central"  }
    maven { url "https://maven.aliyun.com/repository/jcenter"  }
    maven { url "https://dl.bintray.com/kotlin/ktor" }
}

Ktor应用安装到Docker

  1. gradle build
  2. vim Dockerfile
  3. docker build --tag=hellokt .
  4. docker run -it --rm -p 8080:8080 hellokt

Dockerfile

FROM openjdk:8-jre-alpine
RUN mkdir /app
COPY ./build/libs/hellokt-all.jar /app
WORKDIR /app
CMD ["java", "-jar", "hellokt-all.jar" ]

Ktor使用配置文件(application.conf)

Ktor使用配置文件,需要更改Application入口类,并在配置文件中指明模块,最后通过gradle run命令运行

main.kt

import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.features.CallLogging
import io.ktor.features.DefaultHeaders
import io.ktor.response.respondText
import io.ktor.routing.Routing
import io.ktor.routing.get

/**
 * Created by BaiJiFeiLong@gmail.com at 18-11-18 下午12:10
 */

fun Application.main() {
    install(DefaultHeaders)
    install(CallLogging)
    install(Routing) {
        get("/") {
            call.respondText("Hello ")
        }
    }
}

application.conf

放到Resources根目录

ktor {
  deployment {
    port = 8088
  }

  application {
    modules = [MainKt.main]
  }
}

在gradle构建脚本中更改mainClassName

mainClassName = 'io.ktor.server.netty.EngineMain'

在Ktor项目中使用JWT

build.gradle

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.10"
        classpath "com.github.jengelman.gradle.plugins:shadow:4.0.2"
    }
    repositories {
        maven { url "https://maven.aliyun.com/repository/central" }
        maven { url "https://maven.aliyun.com/repository/jcenter" }
    }
}

apply plugin: 'application'
apply plugin: 'kotlin'
apply plugin: 'com.github.johnrengelman.shadow'

mainClassName = 'io.ktor.server.netty.EngineMain'

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    compile "io.ktor:ktor-server-netty:1.0.0-beta-3"
    compile "io.ktor:ktor-html-builder:1.0.0-beta-3"
    compile "io.ktor:ktor-jackson:1.0.0-beta-3"
    compile "io.ktor:ktor-auth:1.0.0-beta-3"
    compile "io.ktor:ktor-auth-jwt:1.0.0-beta-3"
    compile "ch.qos.logback:logback-classic:1.2.3"
}

repositories {
    maven { url "https://maven.aliyun.com/repository/central" }
    maven { url "https://maven.aliyun.com/repository/jcenter" }
    maven { url "https://dl.bintray.com/kotlin/ktor" }
}

main.kt

import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.fasterxml.jackson.databind.SerializationFeature
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.auth.Authentication
import io.ktor.auth.UserIdPrincipal
import io.ktor.auth.authenticate
import io.ktor.auth.jwt.jwt
import io.ktor.auth.principal
import io.ktor.features.CORS
import io.ktor.features.ContentNegotiation
import io.ktor.features.StatusPages
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.jackson.jackson
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.get
import io.ktor.routing.post
import io.ktor.routing.route
import io.ktor.routing.routing
import java.util.*

/**
 * Created by BaiJiFeiLong@gmail.com at 18-11-18 下午12:10
 */

class InvalidCredentialsException(message: String) : RuntimeException(message)

data class Snippet(val user: String, val text: String)

data class PostSnippet(val snippet: Text) {
    data class Text(val text: String)
}

open class SimpleJwt(val secret: String) {
    private val algorithm = Algorithm.HMAC256(secret)
    val verifier = JWT.require(algorithm).build()
    fun sign(name: String): String = JWT.create().withClaim("name", name).sign(algorithm)
}

class User(val name: String, val password: String)

val users = Collections.synchronizedMap(
        listOf(User("test", "test")).associateBy { it.name }.toMutableMap()
)

class LoginRegister(val user: String, val password: String)

val snippets = Collections.synchronizedList(mutableListOf(
        Snippet("demo", "hello"),
        Snippet("demo", "world")
))

fun Application.main() {
//    install(DefaultHeaders)
//    install(CallLogging)
    val simpleJwt = SimpleJwt("my-super-secret-for-jwt")

    install(ContentNegotiation) {
        jackson {
            enable(SerializationFeature.INDENT_OUTPUT)
        }
    }
    install(Authentication) {
        jwt {
            verifier(simpleJwt.verifier)
            validate {
                UserIdPrincipal(it.payload.getClaim("name").asString())
            }
        }
    }
    install(StatusPages) {
        exception<InvalidCredentialsException> {
            call.respond(HttpStatusCode.Unauthorized, mapOf("OK" to false, "error" to (it.message ?: "")))
        }
    }
    install(CORS) {
        method(HttpMethod.Options)
        method(HttpMethod.Get)
        method(HttpMethod.Post)
        method(HttpMethod.Put)
        method(HttpMethod.Delete)
        method(HttpMethod.Patch)
        header(HttpHeaders.Authorization)
        allowCredentials = true
        anyHost()
    }
    routing {
        route("/snippets") {
            authenticate {
                get {
                    call.respond(mapOf("snippets" to synchronized(snippets) {
                        snippets.toList()
                    }))
                }
            }
            authenticate {
                post {
                    val post = call.receive<PostSnippet>()
                    val principal = call.principal<UserIdPrincipal>() ?: error("No principle")
                    snippets += Snippet(principal.name, post.snippet.text)
                    call.respond(mapOf("OK" to true))
                }
            }
        }

        post("/login-register") {
            val post = call.receive<LoginRegister>()
            val user = users.getOrPut(post.user) { User(post.user, post.password) }
            if (user.password != post.password) throw InvalidCredentialsException("Invalid credentials")
            call.respond(mapOf("token" to simpleJwt.sign(user.name)))
        }
    }
}

Ktor与Websocket

需要添加Websocket的feature:

compile "io.ktor:ktor-websockets:1.0.0-beta-3"

main.kt

import io.ktor.application.Application
import io.ktor.application.install
import io.ktor.http.cio.websocket.DefaultWebSocketSession
import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readText
import io.ktor.routing.routing
import io.ktor.websocket.WebSockets
import io.ktor.websocket.webSocket
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.collections.LinkedHashSet

/**
 * Created by BaiJiFeiLong@gmail.com at 18-11-18 下午12:10
 */

class ChatClient(val session: DefaultWebSocketSession) {
    companion object {
        var lastId = AtomicInteger(0)
    }

    val id = lastId.getAndIncrement()
    val name = "user$id"
}

fun Application.main() {
    install(WebSockets)

    routing {
        val wsConnections = Collections.synchronizedSet(LinkedHashSet<ChatClient>())

        webSocket("/chat") {
            val client = ChatClient(this)
            wsConnections += client
            try {
                while (true) {
                    val frame = incoming.receive()
                    when (frame) {
                        is Frame.Text -> {
                            val text = frame.readText()
                            for (conn in wsConnections) {
                                val txt = wsConnections.map { it.name }.joinToString(", ")
                                conn.session.outgoing.send(Frame.Text(txt))
                            }
                        }
                    }
                }
            } catch (e: Exception) {
                println("Exception: ${e.message}")
            } finally {
                println("A connection has gone")
                wsConnections -= client
            }
        }
    }
}

代码实现的功能:广播消息到每个WS客户端

文章首发: http://baijifeilong.github.io/2018/11/18/kotlin-best-practice/

Kotlin 最佳实践的更多相关文章

  1. 转载:Google 官方应用架构的最佳实践指南 赞👍

    官方给的实践指南,很有实际的指导意义,  特别是对一些小公司,小团队,给了很好的参考意义. 原文地址: https://developer.android.com/topic/libraries/ar ...

  2. ASP.NET跨平台最佳实践

    前言 八年的坚持敌不过领导的固执,最终还是不得不阔别已经成为我第二语言的C#,转战Java阵营.有过短暂的失落和迷茫,但技术转型真的没有想象中那么难.回头审视,其实单从语言本身来看,C#确实比Java ...

  3. 《AngularJS深度剖析与最佳实践》简介

    由于年末将至,前阵子一直忙于工作的事务,不得已暂停了微信订阅号的更新,我将会在后续的时间里尽快的继续为大家推送更多的博文.毕竟一个人的力量微薄,精力有限,希望大家能理解,仍然能一如既往的关注和支持sh ...

  4. ASP.NET MVC防范CSRF最佳实践

    XSS与CSRF 哈哈,有点标题党,但我保证这篇文章跟别的不太一样. 我认为,网站安全的基础有三块: 防范中间人攻击 防范XSS 防范CSRF 注意,我讲的是基础,如果更高级点的话可以考虑防范机器人刷 ...

  5. 快速web开发中的前后端框架选型最佳实践

    这个最佳实践是我目前人在做的一个站点,主要功能: oauth登录 发布文章(我称为"片段"),片段可以自定义一些和内容有关的指标,如“文中人物:12”.支持自定义排版.插图.建立相 ...

  6. Spring Batch在大型企业中的最佳实践

    在大型企业中,由于业务复杂.数据量大.数据格式不同.数据交互格式繁杂,并非所有的操作都能通过交互界面进行处理.而有一些操作需要定期读取大批量的数据,然后进行一系列的后续处理.这样的过程就是" ...

  7. Atitit.log日志技术的最佳实践attilax总结

    Atitit.log日志技术的最佳实践attilax总结 1. 日志的意义与作用1 1.1. 日志系统是一种不可或缺的单元测试,跟踪调试工具1 2. 俩种实现[1]日志系统作为一种服务进程存在 [2] ...

  8. PHP核心技术与最佳实践——全局浏览

    难得买到并喜欢一本好书,‘PHP核心技术与最佳实践’. 几天时间,先看了个大概,总结一下整体是什么样子的,怎么看怎么学. 1.总共14章: 2.第1.2章讲PHP的OOP: 其中第一章侧重于PHP的O ...

  9. Abp集成Swagger的最佳实践

    1.在项目中添加nuget包 Abp.Web.Api.SwaggerTool 2.在项目Abp模块的DependsOn添加AbpWebApiSwaggerToolModule Run It,启动项目, ...

随机推荐

  1. 251. Flatten 2D Vector 平铺二维矩阵

    [抄题]: Implement an iterator to flatten a 2d vector. Example: Input: 2d vector = [ [1,2], [3], [4,5,6 ...

  2. python小练习:使用循环和函数实现一个摇骰子小游戏。游戏规则如下:游戏开始,首先玩家选择Big or Small(押大小),选择完成后开始摇三个骰子,计算总值,11<=总值<=18为“大”,3<=总值<=10为“小”。然后告诉玩家猜对或者是猜错的结果。

    python小练习:使用循环和函数实现一个摇骰子小游戏.游戏规则如下:游戏开始,首先玩家选择Big or Small(押大小),选择完成后开始摇三个骰子,计算总值,11<=总值<=18为“ ...

  3. Python3,x:Fiddler抓包工具如何进行手机APP的数据爬取

    示例一:苹果手机抓取教程 https://www.cnblogs.com/lizm166/p/8693085.html https://blog.csdn.net/cui130/article/det ...

  4. 现代编译原理--第六章(中间树 IR Tree 含源码)

    (转载请表明出处   http://www.cnblogs.com/BlackWalnut/p/4559717.html ) 这一章,就虎书而言,理论知识点是及其少的,就介绍了为什么要有一个中间表示树 ...

  5. spring源码分析之初始化过程

    1.org.springframework.web.context.ContextLoaderListener 一个ServletContextListener,web容器启动监听器 1.1内有成员C ...

  6. 《C#从现象到本质》读书笔记(七)第9章 泛型

    <C#从现象到本质>读书笔记(七)第9章 泛型 泛型的三大好处:类型安全,增强性能(避免装箱和拆箱),代码复用. 泛型方法是传入的参数至少有一个类型为T(尚未制定的类型,根据微软的命名规则 ...

  7. 学习:java集合

    java是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由Sun Microsystems公司于1995年5月推出的Java程序设计语言和Java平台(即JavaEE, JavaME, Jav ...

  8. Python12/25--前端之BOM/DOM

    一.DOM 1. 什么是DOM 文档对象模型 Document Object Model 文档对象模型 是表示和操作 HTML和XML文档内容的基础API 文档对象模型,是W3C组织推荐的处理可扩展标 ...

  9. Python11/12--GIL/互斥锁/进程池

    GIL1.全局解释器锁? 锁就是线程里面那个锁 锁是为了避免资源竞争造成数据的错乱 2.python程序的执行过程? 1.启动解释器进程 python.exe 2.解析你的py文件并执行它 每个py程 ...

  10. 在Unity5.6.5f1中使用C#7语法

    备忘,记忆力越来越差了,必需把这种琐碎的东西记下来,以防1年后想再用完全没头绪. 之前试过用C#6语法,但是怎么配置操作的完全没印象了. 首先去这下载扩展 https://bitbucket.org/ ...