1. 和 Spring Boot Kotlin 对比
没有 Spring Boot 那么省心,有些代码需要自己封装,但是也比较方便,废话少说,直接上代码。
协程很好的消除了回调地狱,比写 future 代码要简洁很多,同样的也好理解。本文用的vertx版本是 3.9.2
2. 依赖
<?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.sweet</groupId>
<artifactId>vertxkt</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.version>1.3.72</kotlin.version>
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>
<maven-shade-plugin.version>2.4.3</maven-shade-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<exec-maven-plugin.version>1.5.0</exec-maven-plugin.version>
<vertx.version>3.9.2</vertx.version>
<junit-jupiter.version>5.4.0</junit-jupiter.version>
<log.version>2.8.2</log.version>
<main.verticle>com.sweet.vertxkt.MainVerticle</main.verticle>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-mysql-client</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-lang-kotlin-coroutines</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-lang-kotlin</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>${log.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jul</artifactId>
<version>${log.version}</version>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.6</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<configuration>
<jvmTarget>1.8</jvmTarget>
</configuration>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>io.vertx.core.Launcher</Main-Class>
<Main-Verticle>${main.verticle}</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>
<artifactSet>
</artifactSet>
<outputFile>${project.build.directory}/${project.artifactId}-${project.version}-fat.jar
</outputFile>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>${exec-maven-plugin.version}</version>
<configuration>
<mainClass>io.vertx.core.Launcher</mainClass>
<arguments>
<argument>run</argument>
<argument>${main.verticle}</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
3. 编写启动类
package com.sweet.vertxkt
import io.vertx.core.Vertx
import io.vertx.kotlin.core.deployVerticleAwait
import io.vertx.kotlin.coroutines.CoroutineVerticle
fun main() {
System.setProperty("vertx.logger-delegate-factory-class-name", "io.vertx.core.logging.Log4j2LogDelegateFactory");
Vertx.vertx().deployVerticle(MainVerticle())
}
class MainVerticle : CoroutineVerticle() {
override suspend fun start() {
vertx.deployVerticle(DBService())
vertx.deployVerticleAwait(HttpServerVerticle())
}
}
4. 数据访问类
因为没有模型类,直接用的json一把梭
package com.sweet.vertxkt
import io.vertx.core.eventbus.EventBus
import io.vertx.core.eventbus.Message
import io.vertx.core.json.JsonArray
import io.vertx.core.json.JsonObject
import io.vertx.core.logging.LoggerFactory
import io.vertx.kotlin.coroutines.*
import io.vertx.kotlin.sqlclient.beginAwait
import io.vertx.kotlin.sqlclient.commitAwait
import io.vertx.kotlin.sqlclient.executeAwait
import io.vertx.mysqlclient.MySQLConnectOptions
import io.vertx.mysqlclient.MySQLPool
import io.vertx.sqlclient.*
import kotlinx.coroutines.launch
class DBService : CoroutineVerticle() {
private val logger = LoggerFactory.getLogger(this.javaClass)
override suspend fun start() {
super.start()
val mysqlConf = JsonObject()
.put("port", 3306)
.put("host", "localhost")
.put("database", "cat1")
.put("user", "root")
.put("password", "123456");
val connectOptions = MySQLConnectOptions(mysqlConf)
val poolOptions = PoolOptions(
JsonObject().put("maxSize", 5))
val sqlClient = MySQLPool.pool(vertx, connectOptions, poolOptions)
val eventBus = vertx.eventBus()
dbCreateEvent(eventBus, sqlClient)
dbQueryEvent(eventBus, sqlClient)
dbAutoTx(eventBus, sqlClient)
}
private fun dbAutoTx(eventBus: EventBus, sqlClient: MySQLPool) {
eventBus.awaitLocalConsumer<JsonArray>("db.autoTx") {
logger.info("dbAutoTx for: {}", it.body())
val array = dbAutoTxHandler(it.body(), sqlClient)
it.reply(array)
}
}
private fun dbCreateEvent(eventBus: EventBus, sqlClient: MySQLPool) {
eventBus.awaitLocalConsumer<JsonArray>("db.create") {
logger.info("dbCreateEvent for: {}", it.body())
dbCreateEventHandler(it, sqlClient)
}
}
private fun dbQueryEvent(eventBus: EventBus, sqlClient: MySQLPool) {
eventBus.awaitLocalConsumer<String>("db.query") {
dbQueryItemHandler(it, sqlClient)
}
}
private suspend fun dbCreateEventHandler(message: Message<JsonArray>, sqlClient: MySQLPool) {
val list = message.body()
logger.info("dbCreateEventHandler: {}", list)
var beginAwait: Transaction? = null
try {
beginAwait = sqlClient.beginAwait()
for (item in list) {
val executeAwait = beginAwait.query(item.toString()).executeAwait()
}
beginAwait.commitAwait()
message.reply(message.body())
} catch (e: Exception) {
logger.error("dbCreateEventHandlerError: ", e)
logger.error("catch 0000")
message.reply(JsonArray().add("ERROR0"))
beginAwait?.rollback()
} finally {
beginAwait?.close()
}
}
private suspend fun dbQueryItemHandler(message: Message<String>, sqlClient: MySQLPool) {
try {
val rowSet = sqlClient.query(message.body()).executeAwait()
val list = rowSet.map {
val id = it.getLong("id")
val name = it.getString("name")
val age = it.getInteger("age")
JsonObject()
.put("id", id)
.put("name", name)
.put("age", age)
}
message.reply(JsonArray(list))
} catch (e: Exception) {
e.printStackTrace()
message.reply("[0]")
}
}
private suspend fun dbAutoTxHandler(array: JsonArray, sqlClient: MySQLPool): JsonArray {
val resultArray = JsonArray()
sqlClient.autoTx({ tx ->
for (any in array) {
tx.query(any.toString()).executeAwait()
}
resultArray.add("SUCCESS")
}, { exception ->
logger.error("dbAutoTxHandlerError", exception)
resultArray.clear().add("ERROR->${exception.message?.substring(0, 10)}")
})
return resultArray
}
// 扩展方法:自动管理事务
private suspend fun Pool.autoTx(f: suspend (Transaction) -> Unit, ex: (Exception) -> Unit) {
var beginAwait: Transaction? = null
try {
beginAwait = this.beginAwait()
logger.info("autoTx::begin")
f(beginAwait)
beginAwait.commitAwait()
logger.info("autoTx::commit")
} catch (e: Exception) {
logger.error("autoTx::Error:: {}", e.message)
beginAwait?.rollback()
logger.info("autoTx::rollback")
ex(e)
} finally {
beginAwait?.close()
logger.info("autoTx::close")
}
}
// 扩展方法:等待 EventBus 上的事件
private fun <T> EventBus.awaitLocalConsumer(address: String, handler: suspend (Message<T>) -> Unit) {
this.localConsumer<T>(address) {
launch(vertx.dispatcher()) {
handler(it)
}
}
}
}
扩展方法真香,实现了自动提交事务,以及协程等待EventBus上的事件
5. HTTP Server 类
实现简单的 CRUD,以及事务操作
package com.sweet.vertxkt
import io.vertx.core.json.JsonArray
import io.vertx.core.logging.LoggerFactory
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.eventbus.requestAwait
import io.vertx.kotlin.core.http.listenAwait
import io.vertx.kotlin.coroutines.CoroutineVerticle
import io.vertx.kotlin.coroutines.dispatcher
import kotlinx.coroutines.launch
class HttpServerVerticle : CoroutineVerticle() {
private val logger = LoggerFactory.getLogger(this.javaClass)
override suspend fun start() {
val eventBus = vertx.eventBus()
val router = Router.router(vertx)
router.route().handler(BodyHandler.create())
.failureHandler {
logger.error(it.failure())
it.response().end("ERROR")
}
router.route("/query")
.coroutineHandler {
val value = it.bodyAsJson.getValue("sql")
logger.debug("value: {}", value)
val resp = eventBus.requestAwait<JsonArray>("db.query", value)
it.response().end(resp.body().encodePrettily())
}
router.route("/user")
.coroutineHandler {
val array = it.bodyAsJsonArray
logger.debug("value: {}", array)
val resp = eventBus.requestAwait<JsonArray>("db.create", array)
it.response().end(resp.body().encodePrettily())
}
router.route("/user2")
.coroutineHandler {
val array = it.bodyAsJsonArray
logger.debug("value: {}", array)
val resp = eventBus.requestAwait<JsonArray>("db.autoTx", array)
it.response().end(resp.body().encodePrettily())
}
val httpServer = vertx
.createHttpServer()
.requestHandler(router)
.listenAwait(8090)
logger.info("http server listen port: {}", httpServer.actualPort())
}
override suspend fun stop() {
}
private fun Route.coroutineHandler(fn: suspend (RoutingContext) -> Unit) {
handler { ctx ->
launch(ctx.vertx().dispatcher()) {
try {
fn(ctx)
} catch (e: Exception) {
ctx.fail(e)
}
}
}
}
}
6. log4j2 日志配置
resource目录下创建log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Properties>
<Property name="log-pattern">%d{yyyy-MM-dd HH:mm:ss} |- %highlight{%5p}{TRACE=blue, DEBUG=green, INFO=green, WARN=yellow, ERROR=red, FATAL=red} in %style{%C{1}:%L}{cyan} [%style{%t}{magenta}] - %m%n</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${log-pattern}" />
</Console>
<RollingRandomAccessFile name="FILE" fileName="logs/app.log" append="true" filePattern="logs/lsp_app_%i.log.%d{yyyyMMdd}">
<PatternLayout>
<Pattern>%d %-5p [%c] %m%n</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="10MB" />
</Policies>
</RollingRandomAccessFile>
</Appenders>
<!-- Logger levels: trace, debug, info, warn, error, fatal -->
<Loggers> <!--name 是你需要打log的包名-->
<AsyncLogger name="com.sweet" level="DEBUG" additivity="false" includeLocation="true">
<AppenderRef ref="Console" />
<AppenderRef ref="FILE" />
</AsyncLogger>
<AsyncLogger name="io.vertx.sqlclient" level="INFO" additivity="false" includeLocation="true">
<AppenderRef ref="Console" />
</AsyncLogger>
<Root level="WARN">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
7. 打成可执行 jar
mvn clean package
8. 运行 jar
java -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.Log4j2LogDelegateFactory -jar xxxx.jar
这里的 -D 参数为了让我们配置的日志生效