Vert.x Kotlin 协程

协程

轻量级线程,相比传统Java线程,它不会阻塞内核线程,而且可以让异步的代码写起来和同步代码一样舒服。这是官方的文档 kotlin

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xiaoniu.kt</groupId>
    <artifactId>vertx-kt</artifactId>
    <version>3.5.0</version>

    <properties>
        <slf4j.version>1.7.21</slf4j.version>
        <kotlin.version>1.2.0</kotlin.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <log4j.version>2.8.2</log4j.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-lang-kotlin</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-lang-kotlin-coroutines</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-web</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-web-client</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-mysql-postgresql-client</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!-- log4j2 日志配置 start -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-jcl</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-jul</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.3.6</version>
        </dependency>
        <!-- log4j2 日志配置 end -->

    </dependencies>

    <build>
        <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
        <plugins>
            <plugin>
                <artifactId>kotlin-maven-plugin</artifactId>
                <groupId>org.jetbrains.kotlin</groupId>
                <version>${kotlin.version}</version>
                <configuration>
                    <jvmTarget>1.8</jvmTarget>
                </configuration>
                <executions>
                    <execution>
                        <id>compile</id>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <!-- Run shade goal on package phase -->
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <finalName>kotlin-coroutines-examples</finalName>
                            <createDependencyReducedPom>false</createDependencyReducedPom>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <manifestEntries>
                                        <Main-Class>io.vertx.core.Launcher</Main-Class>
                                        <Main-Verticle>com.xiaoniu.kt.RestVerticle</Main-Verticle>
                                    </manifestEntries>
                                </transformer>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/services/io.vertx.core.spi.VerticleFactory</resource>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <profiles>
        <profile>
            <id>staging</id>
            <repositories>
                <repository>
                    <id>staging</id>
                    <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
                </repository>
            </repositories>
        </profile>
    </profiles>

</project>
复制代码
首先来一个普通版的Rest服务
import io.vertx.core.AbstractVerticle
import io.vertx.core.Handler
import io.vertx.core.Vertx
import io.vertx.core.http.HttpMethod
import io.vertx.core.logging.Log4j2LogDelegateFactory
import io.vertx.core.logging.LoggerFactory
import io.vertx.ext.web.Router
import io.vertx.ext.web.RoutingContext
import io.vertx.ext.web.handler.BodyHandler
import io.vertx.ext.web.handler.CorsHandler
import org.apache.logging.log4j.LogManager
import java.time.LocalDateTime

/**
 * Created by sweet on 2017/12/20.
 * ---------------------------
 */
fun main(args: Array<String>) {
  System.setProperty("vertx.logger-delegate-factory-class-name",
    "io.vertx.core.logging.Log4j2LogDelegateFactory")
  Vertx.vertx().deployVerticle(RestVerticle())
}

class RestVerticle : AbstractVerticle() {

  val logger = LoggerFactory.getLogger(this.javaClass)
//  val log = LogManager.getLogger(this.javaClass)

  override fun start() {

    var server = vertx.createHttpServer()
    var router = Router.router(vertx)

    // 绑定父路由
    router.mountSubRouter("/api", router)

    router.route("/*").handler({
      println("可以在此处处理权限的问题")
      it.next()
    }).handler(CorsHandler.create("*").allowedMethod(HttpMethod.GET) // 跨域处理
        .allowedMethod(HttpMethod.POST)
        .allowedMethod(HttpMethod.PUT)
        .allowedMethod(HttpMethod.DELETE)
        .allowedMethod(HttpMethod.OPTIONS)
        .allowedHeader("X-PINGARUNER")
        .allowedHeader("Content-Type"))
      .failureHandler(errorHandler)  // 给 / 开头的路由绑定 错误处理器

    // POST 获取 请求体 必须设置
    router.route().handler(BodyHandler.create().setUploadsDirectory("file-cache").setBodyLimit(5000000))

    // /api/hello 子路由
    router.get("/hello").handler({
      it.response().end("Hello Api")
    })

    // /api/world 子路由
    router.get("/world").handler({
      1/0
      it.response().end("World Api")
    })

    // /api/post 子路由
    router.post("/post").handler({
      logger.debug("/post")
      var jsonObject = it.bodyAsJson
      println("body: ${jsonObject.encode()}")
      it.response().end(jsonObject.encodePrettily())
    })

    // /api/file 子路由 文件上传
    router.post("/file").handler({
      it.fileUploads().forEach {
        println("fileName: ${it.fileName()}")
        println("name: ${it.name()}")
        println("size: ${it.size()}")
        println("uploadFileName: ${it.uploadedFileName()}\n")
      }
      it.response().end("OK")
    })

    server.requestHandler(router::accept).listen(config().getInteger("port", 8080), {
      if (it.succeeded()) {
        logger.info("Server start ${it.result().actualPort()}")
      } else {
        it.cause().printStackTrace()
        logger.info("Server error ${it.cause().message}")
      }
    })
  }

  override fun stop() {

  }

  // 错误处理的两种写法, 相比 Java里面的捕获异常 在 catch中写的代码
  fun errorFun(rx : RoutingContext) {
    rx.failure().printStackTrace()
    println("uri ${rx.request().uri()}")
    rx.response().end("ERROR ${LocalDateTime.now()}")
  }

  val errorHandler = Handler<RoutingContext> {
    it.failure().printStackTrace()
    println("uri ${it.request().uri()}")
    it.response().end("ERROR ${LocalDateTime.now()}")
  }
  //-------------------------------------------------------
}
复制代码
协程版 Rest服务
import io.vertx.core.Handler
import io.vertx.core.Vertx
import io.vertx.core.logging.LoggerFactory
import io.vertx.ext.asyncsql.MySQLClient
import io.vertx.ext.sql.ResultSet
import io.vertx.ext.sql.SQLClient
import io.vertx.ext.web.Route
import io.vertx.ext.web.Router
import io.vertx.ext.web.RoutingContext
import io.vertx.ext.web.handler.BodyHandler
import io.vertx.kotlin.core.json.array
import io.vertx.kotlin.core.json.json
import io.vertx.kotlin.core.json.obj
import io.vertx.kotlin.coroutines.CoroutineVerticle
import io.vertx.kotlin.coroutines.awaitResult
import io.vertx.kotlin.coroutines.dispatcher
import kotlinx.coroutines.experimental.launch
import java.time.LocalDateTime


/**
 * 重点 演示对 MySQL 的操作, 利用 协程 写出类似同步的代码
 * Created by sweet on 2017/12/21.
 * ---------------------------
 */
fun main(args: Array<String>) {
  System.setProperty("vertx.logger-delegate-factory-class-name",
    "io.vertx.core.logging.Log4j2LogDelegateFactory")
  Vertx.vertx().deployVerticle(RestCoroutineVerticle())
}

class RestCoroutineVerticle : CoroutineVerticle() {

  private val logger = LoggerFactory.getLogger(this.javaClass)

  private lateinit var client: SQLClient

  suspend override fun start() {
    client = MySQLClient.createShared(vertx, json {
      obj(
        "host" to "127.0.0.1",
        "port" to 3306,
        "username" to "root",
        "password" to "main",
        "database" to "kt-demo"
      )
    })

    val router = Router.router(vertx)
    router.route().handler(BodyHandler.create())
      .failureHandler(errorHandler)

    router.get("/student/:id").handler { getStudent(it) }
    router.post("/student").coroutineHandler { createStudent(it) }
    router.delete("/student/:id").coroutineHandler { deleteStudent(it) }
    router.put("/student/:id").coroutineHandler { updateStudent(it) }

    router.get("/teacher/:id").coroutineHandler { getTeacher(it) }
    router.get("/teachers").coroutineHandler { getTeachers(it) }

    // 演示事务
    router.post("/tx").coroutineHandler { tx(it) }

    router.get("/test/error").handler({
      1 / 0
      it.response().end("OK")
    })

    vertx.createHttpServer()
      .requestHandler(router::accept)
      .listen(8080, {
        if (it.succeeded()) {
          logger.info("http server start !!! ${it.result().actualPort()}")
        } else {
          logger.error("http server error :: " + it.cause().message)
          it.cause().printStackTrace()
        }
      })
  }

  private suspend fun tx(context: RoutingContext) {

    var bodyAsJson = context.bodyAsJson
    var school = bodyAsJson.getJsonArray("school")
    var teacher = bodyAsJson.getJsonArray("teacher")

    var connection = getConnection(client)
    connection.use {
      beginTx(connection)
      try {

        var schoolResult = updateWithParams(connection,
          "INSERT INTO t_school (name) VALUES (?)",
          json { array(school.list.get(0)) }
        )

        var teacherResult = updateWithParams(connection,
          "INSERT INTO t_teacher (name, school_id) VALUES (?,?)",
          json { teacher }
        )

        logger.debug("school result: ${schoolResult.toJson().encodePrettily()}")
        logger.debug("teacher result: ${teacherResult.toJson().encodePrettily()}")
        commitTx(connection)

        context.response().end("ok")

      } catch (e: Exception) {
        e.printStackTrace()
        rollbackTx(connection)
        context.response().setStatusCode(500).end()
      }
    }
  }

  private suspend fun getTeachers(context: RoutingContext) {
    val teacherResultSet = query(client,
      "SELECT id, name, school_id FROM t_teacher"
    )

    var result = teacherResultSet?.getRows()
    result.forEach {
      var tId = it.getInteger("id")
      var studentResultSet = queryWithsParams(client,
        "SELECT name, age FROM t_student WHERE teacher_id = ?",
        json { array(tId) }
      )

      var studentResult = studentResultSet?.getRows()
      it.put("student", studentResult)
    }
    logger.debug(result.toString())
    context.response().end(result.toString())
  }

  private suspend fun getTeacher(context: RoutingContext) {
    val id = context.pathParam("id")
    val teacherResultSet = queryWithsParams(client,
      "SELECT id, name, school_id FROM t_teacher WHERE id = ?",
      json { array(id) }
    )

    var result = teacherResultSet?.getRows()
    result.forEach {
      var tId = it.getInteger("id")
      var studentResultSet = queryWithsParams(client,
        "SELECT name, age FROM t_student WHERE teacher_id = ?",
        json { array(tId) }
      )

      var studentResult = studentResultSet?.getRows()
      it.put("student", studentResult)
    }

    logger.debug(result.toString())
    context.response().end(result.toString())
  }

  private suspend fun updateStudent(context: RoutingContext) {
    val id = context.pathParam("id")
    val bodyAsJsonArray = context.bodyAsJsonArray
    bodyAsJsonArray.add(id)
    val result = updateWithParams(client,
      "UPDATE t_student SET name = ?, age = ?, school_id = ?, teacher_id = ? WHERE id = ?",
      json { bodyAsJsonArray }
    )

    context.response().end(result.toJson().encodePrettily())
  }

  private suspend fun deleteStudent(context: RoutingContext) {
    val id = context.pathParam("id")
    val result = updateWithParams(client,
      "DELETE FROM t_student WHERE id = ?",
      json { array(id) }
    )

    context.response().end(result.toJson().encodePrettily())
  }

  private suspend fun createStudent(context: RoutingContext) {
    val bodyAsJsonArray = context.bodyAsJsonArray

    val result = updateWithParams(client,
      "INSERT INTO t_student (name, age, school_id, teacher_id) VALUES (?,?,?,?)",
      json { bodyAsJsonArray }
    )
    context.response().end(result.toJson().encodePrettily())
  }

  private fun getStudent(context: RoutingContext) {
    val id = context.pathParam("id")
    launch(context.vertx().dispatcher()) {
      val result = awaitResult<ResultSet> {
        client.queryWithParams("SELECT id, name, age FROM t_student WHERE id = ?",
          json { array(id) }, it)
      }
      if (result.rows.size == 1) {
        context.response().end(result.getRows().get(0).encodePrettily())
      } else {
        context.response().setStatusCode(404).end()
      }
    }
  }

  val errorHandler = Handler<RoutingContext> {
    it.failure().printStackTrace()
    println("uri ${it.request().uri()}")
    it.response().end("ERROR ${LocalDateTime.now()}")
  }

}

fun Route.coroutineHandler(fn: suspend (RoutingContext) -> Unit) {
  handler { ctx ->
    launch(ctx.vertx().dispatcher()) {
      try {
        fn(ctx)
      } catch (e: Exception) {
        ctx.fail(e)
      }
    }
  }
}
复制代码
DbHandler.java

这里面封装了一些方便操作MySQL的函数, 函数是一等公民的感觉真好。

import io.vertx.core.json.JsonArray
import io.vertx.ext.sql.ResultSet
import io.vertx.ext.sql.SQLClient
import io.vertx.ext.sql.SQLConnection
import io.vertx.ext.sql.UpdateResult
import io.vertx.kotlin.coroutines.awaitResult


/**
 * Created by sweet on 2017/12/22.
 * ---------------------------
 */

suspend fun getConnection(client: SQLClient) : SQLConnection {
  return awaitResult {
    client.getConnection(it)
  }
}
suspend fun queryWithParams(connection: SQLConnection, sql: String, args: JsonArray) : ResultSet {
  return awaitResult {
    connection.queryWithParams(sql, args, it)
  }
}

suspend fun queryWithsParams(client: SQLClient, sql: String, args: JsonArray) : ResultSet {
  return awaitResult {
    client.queryWithParams(sql, args, it)
  }
}

suspend fun query(connection: SQLConnection, sql: String) : ResultSet {
  return awaitResult {
    connection.query(sql, it)
  }
}

suspend fun query(client: SQLClient, sql: String) : ResultSet {
  return awaitResult {
    client.query(sql, it)
  }
}

suspend fun updateWithParams(connection: SQLConnection, sql: String, args: JsonArray) : UpdateResult {
  return awaitResult {
    connection.updateWithParams(sql, args, it)
  }
}

suspend fun updateWithParams(client: SQLClient, sql: String, args: JsonArray) : UpdateResult {
  return awaitResult {
    client.updateWithParams(sql, args, it)
  }
}

suspend fun update(connection: SQLConnection, sql: String) : UpdateResult {
  return awaitResult {
    connection.update(sql, it)
  }
}

suspend fun update(client: SQLClient, sql: String) : UpdateResult {
  return awaitResult {
    client.update(sql, it)
  }
}

suspend fun beginTx(connection: SQLConnection) {
  awaitResult<Void> {
    connection.setAutoCommit(false, it)
  }
}

suspend fun commitTx(connection: SQLConnection) {
  awaitResult<Void> {
    connection.commit(it)
  }
}

suspend fun rollbackTx(connection: SQLConnection) {
  awaitResult<Void> {
    connection.rollback(it)
  }
}

suspend fun executeSQL(connection: SQLConnection, sql: String) {
  awaitResult<Void> {
    connection.execute(sql, it)
  }
}
复制代码

上面的代码是对 MySQL的简单操作,只是演示一下协程给代码带来的优势。如果不用协程组装数据简直是噩梦,要写很多 Future,说一下 awaitResult 里面的泛型,其实就是包在里面的方法的回调函数里的类型。多说无意,多写写就明白了,其实我看 Kotlin也才两天,有哪些智障的写法一定给我指出来,谢谢啦~

SQL
create table t_school
(
	id int auto_increment
		primary key,
	name varchar(60) not null
)
;

create table t_student
(
	id int auto_increment
		primary key,
	name varchar(50) not null,
	age int not null,
	school_id int not null,
	teacher_id int null
)
;

create table t_teacher
(
	id int auto_increment
		primary key,
	name varchar(50) not null,
	school_id int not null
)
;

create table t_user
(
	id int auto_increment
		primary key,
	name varchar(50) not null,
	age int not null
)
;
复制代码

表是随意建的,只是为了演示一对多关系。

直接运行 Main方法
打成 Jar 包
mvn clean package
复制代码

java -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.Log4j2LogDelegateFactory -jar xxx.jar 就可以正常运行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值