代码已开源: https://github.com/wangyuheng/ddl2plantuml
背景
之前用 kotlin
开发过一款根据建表 DDL 语句生成 plantuml ER 图的应用。被问如何使用,答曰"给你一个 jar 包,然后执行 java -jar ddl2plantuml.jar ./ddl.sql ./er.puml
就可以了。是不是 so easy?"
结果被吐槽了一番,
- 为什么不能像命令行应用一样提供相关帮助信息?
- 为什么是 Java, 而不是一个原生命令行应用?
这个吐槽带来了一个思考: 为什么 Java 很少用于开发原生命令行(CLI)应用呢?我认为主要问题有 2 个
- Java 通过 JVM 实现跨平台。也就是说,如果要使用 Java 应用需要先安装 JRE。
- Java 的优势在于 JVM 热点代码检测和运行时编译及优化,所以这是一门程序运行时间越长,速度越快的神奇语言。而付出的代价则是应用启动速度较慢。这与一次性启动运行的命令行应用的场景需求正相反。
方案
为了解决上述问题,引入 2 个名词
Picocli
GraalVM
Picocli
Picocli 致力于提供“最简便的方式来创建富命令行应用,这种应用可以在 JVM 上和 JVM 之外运行”
使用起来非常简单
fun main(args: Array<String>) {
val cmd = CommandLine(Convert())
when {
args.isEmpty() -> {
cmd.usage(System.out)
}
else -> {
val exitCode = cmd.execute(*args)
exitProcess(exitCode)
}
}
}
@CommandLine.Command(name = "ddl2plantuml",
version = ["软件名称:Ddl2plantuml\n版本:V1.1.0"],
description = ["convert sql ddl to plantuml er"],
mixinStandardHelpOptions = true
)
class Convert : Callable<Int> {
@CommandLine.Parameters(index = "0", description = ["The sql ddl file that should be convert to plantuml er."])
lateinit var src: Path
@CommandLine.Option(names = ["-o", "--output"], description = ["The file where the plantuml file to be saved. default is console "])
private var target: Path? = null
override fun call(): Int {
require(src.toFile().exists()) { "ddl file must be existed!" }
when (target) {
null -> {
FileReader(src).read()
.apply { ConsoleWriter(this).write() }
}
else -> {
FileReader(src).read()
.apply { FileWriter(target!!, this).write() }
}
}
return 0
}
}
效果
这里介绍用到的几个注解及概念
-
@Parameters
和@Options
都是用来定义参数,区别在于@Parameters
根据位置区分,而@Options
可以指定名称 -
退出码。
call()
方法返回的 0 表示退出码,用来描述命令行应用的执行结果。通常用 0 表示成功,其他数字为自定义异常。退出码不会影响程序的执行,但是有一个很实用的功能是当你通过连接的方式同时执行多个应用时,一个非零的退出码会中断这个组合。如:./ddl2plantuml_mac ddl.sql |grep "table"
-
版本及帮助信息。可以自定义并指定样式,version 可以通过
versionProvider
自定义生成。
GraalVM
Go
的一个宣传点是可以将程序编译为一个静态可执行文件,而 Java 也可以通过 GraalVM
做到这一点
GraalVM: Run Programs Faster Anywhere
这个 slogan 和 Java 的"Write Once, Run Anywhere"遥相呼应,同时又展示了极大的野心,准备带来下一个 20 年的辉煌。
GraalVM 是一个高性能的通用虚拟机,可以运行使用 JavaScript,Python 3,Ruby,R,基于 JVM 的语言以及基于 LLVM 的语言开发的应用。 GraalVM 消除了编程语言之间的隔离性,并且通过共享运行时增强了他们的互操作性。它可以独立运行,也可以运行在 OpenJDK,Node.js,Oracle,MySQL 等环境中。
可以看到 GraalVM
提供了非常强大的功能,这里我们不做展开介绍,只看如何解决我们遇到的问题。主要用到了 2 个功能特性
- 即时编译,提升程序启动速度
- Native Image,将应用编译为单个静态可执行文件
使用方式
- 安装 GraalVM
- 安装 native-image 工具
gu install native-image
- 编译应用
native-image -jar target/ddl2plantuml-1.1.0.jar ddl2plantuml
编译后的 native image 不运行在 Java VM 上,但是包含了必要的组件,如内存管理和线程调度,这些组件来自另一个 Substrate VM
。这个过程称为提前编译
此时我们已经得到了一个可以直接执行的原生命令行应用
./ddl2plantuml_mac ddl.sql
注意:
native image 不支持 Java 的所有特性,尤其是对 reflection
的限制。在这次改造过程中,原来通过阿里的 druid
进行 sql 解析,但是 druid
使用了大量的 reflection
导致 native image 编译失败,所以改用 jsqlparser
。
其他
- Picocli 提供了 maven 插件
native-image-maven-plugin
,用于编译阶段进行 native image 构建。但是建议分离开发和构建,在CICD
中执行构建过程,可以节省开发时间,并构建不同平台的应用,解决开发环境局限 - 除了构建命令行应用,GraalVM 也带来了更多的可能性,比如 Java 在
FAAS
中的应用。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于