Kotlin很煩,Gralde很煩,還都是升級狂,加一塊更煩。幾個月不接觸Kotlin,再次上手時便一片迷茫。因此記錄此文,以便再次上手時查閱。html
mkdir hellokt
建立項目文件夾cd hellokt
切換到項目根目錄gradle init --type java-application
使用Gradle初始化Java項目rm -rf src/main/java src/test/java gradle gradlew gradlew.bat
刪除Java目錄和GradleWrapper配置vim build.gradle
編輯Gradle項目配置mkdir -p src/main/kotlin src/test/kotlin
建立Kotlin目錄vim src/main/kotlin/App.kt
編寫Kotlin版HelloWorldgradle clean build run
使用Gradle清理、構建、運行,直接運行也可idea .
用IntelliJ IDEA 打開項目,全部選項均選擇默認,開始用IDE進行開發用圖形化界面建立項目變量太多,人品很差容易掉坑裏。用命令行建立項目,能夠明確每一個文件、每行代碼的用途,整個過程可重現、可控制,還能夠避免在IDE裏某個步驟卡死半天沒反應又結束不掉的尷尬。java
我不Care你的Gradle版本。編譯不過我天然會升級Gradle構建腳本git
build.gradlegithub
// 注意,這個文件是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.ktweb
class App { companion object { @JvmStatic fun main(args: Array<String>) { println("hello kt") } } }
之因此將代碼放到類裏頭,是爲了支持application插件,他須要指定一個含有JVM入口靜態main方法的入口類。spring
也能夠用帶main函數的app.kt,此時mainClassName應配置爲"AppKt"docker
build.gradlevim
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.ktspringboot
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); } } }
注意:websocket
主類必定要放在包裏頭(不能用root或者說default),不然報java.lang.ClassNotFoundException: org.springframework.dao.DataAccessException
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) } } }
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" } }
gradle build
vim Dockerfile
docker build --tag=hellokt .
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入口類,並在配置文件中指明模塊,最後經過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'
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))) } } }
須要添加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/