都2023了!我不允许你还不了解AIGC!立即报名 了解详情
写点什么

如何借助 Graalvm 和 Picocli 构建 Java 编写的原生 CLI 应用

  • 2020-03-26
  • 本文字数:0 字

    阅读完需:约 1 分钟

如何借助Graalvm和Picocli构建Java编写的原生CLI应用

本文要点:


  • 开发人员想要以单个原生可执行性文件的形式分发其命令行应用。

  • GraalVM 可以将 Java 应用编译为单个原生镜像,但是它有一些限制。

  • Picocli 是一个在 JVM 之上编写 CLI 应用的现代库,它有助于克服 GraalVM 的局限性,包括在 Windows 上的局限性。

  • 在 Windows 上搭建 GraalVM 工具链以创建原生镜像的过程并没有很完善的文档。


梦想:可执行的 Java

Go已经成为编写命令行应用的流行编程语言。这可能会有很多的原因,但是 Go 特别棒的一点在于它能够将一个程序编译为单个原生的可执行文件。这使得程序更加易于分发。


长期以来,Java 程序都难以分发,这是因为它们需要在目标机器上安装一个 Java 虚拟机。我们可以将最新的 JVM 与应用捆绑在一起,但是这样的话,包的大小会增加近 200MB。但是,事情正在往好的方向发展:Java 9 引入了 Java 模块系统(Java Module System,JPMS),其中包括了jlink工具,它允许应用创建自定义的、最小化的 JRE,最小可以到 30 至 40MB。Java 14 将会包括jpackage工具,借助该工具可以创建一个安装器(installer),它能够将这个最小化的 JRE 和应用包含在一起。


不过,对于命令行应用来讲,安装器并不理想。理想情况下,我们希望将 CLI 工具分发为“真正的”原生可执行文件,而不需要打包运行时。借助 GraalVM,我们可以让 Java 编写的程序也实现这一点。

GraalVM

GraalVM是一个通用的虚拟机,可以运行 JavaScript、Python、Ruby、R、基于 JVM 的语言(如 Java、Scala、Clojure、Kotlin)和基于 LLVM 的语言(如 C 和 C++)所编写的应用程序。GraalVM 很有意思的一个方面在于,它允许我们混合多种编程语言:程序中的一部分可能会使用 JavaScript、R、Python 或 Ruby 编写,这些组成部分可以通过 Java 进行调用,并且可以彼此共享数据。另一个特性是创建原生镜像的能力,这也是我们将在本文中探讨的内容。

GraalVM 原生镜像

GraalVM原生镜像允许我们提前将 Java 代码编译为一个独立的可执行文件,称为原生镜像。这个可执行文件包含了应用、库、JDK,该文件不会运行在 Java VM 上,而是包含了来自另一个不同虚拟机的必要组件,如内存管理和线程调度,该虚拟机被称为“Substrate VM”。Substrate VM 是运行时组件的名称(如 deoptimizer、垃圾收集器、线程调度等等)。相对于 Java VM,这样形成的程序会有更快的启动时间和更低的运行时内存占用。

原生镜像的限制

为了保证实现的小巧和简洁,并实现积极的前期优化,原生镜像不支持 Java 的所有特性。完整的限制可以参考项目的 GitHub 页面。


其中,有两个限制需要特别注意:



简单来讲,为了创建一个自包含的二进制文件,原生镜像编译器需要预先知道应用的所有类、它们的依赖以及它们所使用的资源。反射和资源通常需要配置。我们随后将会看到一个这样的例子。

Picocli

Picocli是一个现代库和框架,适用于在 JVM 上构建命令行应用。它支持 Java、Groovy、Kotlin 和 Scala。它推出的时间还不到 3 年,但是非常受欢迎,每月的下载量超过了 50 万次。Groovy 语言使用它来实现其CliBuilder DSL。


Picocli 致力于提供“最简便的方式来创建富命令行应用,这种应用可以在 JVM 上和 JVM 之外运行”。它提供了彩色输出、TAB 键自动完成、子命令,与其他的 JVM CLI 相比,它还提供了一些独特的特性,比如可否定选项、重复复合参数组、重复子命令和对引用参数的复杂处理。它的源代码在单个文件中,因此我们可以选择将其作为源代码包含进来,避免添加依赖项。Picocli 对其丰富和细致的文档颇感自豪。



Picocli 用到了反射,因此很容易受到 GraalVM 的 Java 原生镜像的限制,但是它提供了一个注解处理器,该处理器会生成配置文件,从而解决了编译期的限制。

具体的例子

接下来,我们看一个命令行工具的具体样例,它将会使用 Java 编写并编译为单个原生可执行文件。在这个过程中,我们将会看到 picocli 库的一些特性,它们有助于让我们的工具更易于使用。


我们将会构建一个checksum CLI 工具,它能够接收一个名为-a--algorithm的选项和一个表示位置的参数,该参数表示要计算校验和的文件。


我们希望用户能够像使用 C++或其他语言编写的应用那样来使用我们的 Java checksum工具。大致如下所示:


$ echo hi > hi.txt$ checksum -a md5 hi.txt764efa883dda1e11db47671c4a3bbd9e$ checksum -a sha1 hi.txt55ca6286e3e4f4fba5d0448333fa99fc5a404a73
复制代码


这是对命令行应用的最低期望,但是我们不会满足于这种最小公分母式的应用,而是会创建一个让用户满意的出色 CLI 应用。那这意味着什么,我们又该如何实现呢?

出色的应用是有帮助的

我们做出了一些权衡:选择了命令行界面(command line interface,CLI),而不是图形化用户界面(graphical user interface,GUI),这意味着应用对新用户来说并不太易于学习如何使用。我们可以通过提供良好的在线帮助来部分弥补这一不足。


在用户通过-h--help选项请求帮助或者使用了非法的用户输入时,我们的应用应该展示帮助信息。当带有V--version选项的时候,它应该展示版本信息。接下来,我们会看到 picocli 是如何简化该过程的。

用户体验

通过在支持的平台上使用彩色输出,我们可以让应用对用户更加友好。这不仅仅是看上去更漂亮,还能减少用户的认知负担:这种对比会使重要的信息,如命令、选项和参数,能够从周围的文本突出显示出来。


基于 picocli 的应用所生成的帮助信息默认就会使用彩色输出。我们的checksum样例看上去如下所示:



一般而言,应用只有在交互式使用的时候才会输出彩色文本,当执行脚本时,我们不希望日志文件和 ANSI 转义代码混杂在一起。幸运的是,picocli 会自动帮我们处理这个问题。这引入了下一个话题:好的 CLI 应用都设计为能够与其他命令组合使用。

出色的 CLI 应用能够与其他应用协作

Stdout 与 stderr

很多的 CLI 工具都使用标准 I/O 流,这样的话,它们就可以与其他的工具进行组合。通常,细节决定成败。当用户请求帮助的时候,应用应该将使用帮助信息打印到标准输出中。这允许用户将输出以管道的方式输入到其他工具中,如grepless


另一方面,当遇到非法输入的时候,错误信息和使用帮助信息应该打印到标准错误流中:如果我们程序的输出作为其他程序的输入的话,我们不希望错误信息破坏其他的程序。

退出码

当程序结束的时候,它会返回一个退出状态码。通常来讲,值为零的退出码用来表示成功,而非零的退出码则用来表示某种类型的失败。


这就允许用户通过使用&&将多个命令连接在一起,并且在这个序列中如果有命令失败的话,整个序列都会停止。


默认情况下,对于非法的用户输入,picocli 会返回2,而对于应用的业务逻辑中出现的异常则会返回1,否则返回零(一切正常)。当然,在应用中配置其他的退出码是很容易的,但是对于checksum样例来说,默认值就是可以的。


注意,picocli 库并不会调用System.exit,它只是返回一个整数,应用要负责确定是否调用System.exit

紧凑的代码

在上面的章节中,我们描述了很多的功能。你可能会认为需要大量的代码才能完成它们,但是大多数“标准的 CLI 行为”都是由 picocli 库提供的。在我们的应用中,我们所需要做的就是定义选项和位置参数,并通过将类定义为CallableRunnable来实现我们的业务逻辑。在 main 方法中,我们用一行代码就能启动应用:


import picocli.CommandLine;import picocli.CommandLine.Command;import picocli.CommandLine.Option;import picocli.CommandLine.Parameters;import java.io.File;import java.math.BigInteger;import java.nio.file.Files;import java.security.MessageDigest;import java.util.concurrent.Callable;@Command(name = "checksum", mixinStandardHelpOptions = true,      version = "checksum 4.0",  description = "Prints the checksum (MD5 by default) of a file to STDOUT.")class CheckSum implements Callable<Integer> {  @Parameters(index = "0", arity = "1",        description = "The file whose checksum to calculate.")  private File file;  @Option(names = {"-a", "--algorithm"},    description = "MD5, SHA-1, SHA-256, ...")  private String algorithm = "MD5";  // 本样例实现了Callable,所以解析、错误处理以及对使用帮助或版本帮助的用户请求处理  // 都可以在一行代码中实现。  public static void main(String... args) {    int exitCode = new CommandLine(new CheckSum()).execute(args);    System.exit(exitCode);  }  @Override  public Integer call() throws Exception { // the business logic...    byte[] data = Files.readAllBytes(file.toPath());    byte[] digest = MessageDigest.getInstance(algorithm).digest(data);    String format = "%0" + (digest.length*2) + "x%n";    System.out.printf(format, new BigInteger(1, digest));    return 0;  }}
复制代码


我们已经有了一个理想的 Java 工具程序。接下来,我们看一下如何将其转换成一个原生的可执行文件。

原生镜像

对于反射的配置

我们在前面提到过,原生镜像编译器有一些限制:支持反射,但是需要配置


这会影响基于 picocli 的应用:在运行时,picocli 会使用反射去发现所有@Command注解标注的子命令,以及@Option@Parameters注解标注的命令选项和位置参数。


因此,我们需要向 GraalVM 提供一个配置文件,指明所有带注解的类、方法和字段。这样的配置文件如下所示:


[  {    "name" : "CheckSum",    "allDeclaredConstructors" : true,    "allPublicConstructors" : true,    "allDeclaredMethods" : true,    "allPublicMethods" : true,    "fields" : [      { "name" : "algorithm" },      { "name" : "file" }    ]  },  {    "name" : "picocli.CommandLine$AutoHelpMixin",    "allDeclaredConstructors" : true,    "allPublicConstructors" : true,    "allDeclaredMethods" : true,    "allPublicMethods" : true,    "fields" : [      { "name" : "helpRequested" },      { "name" : "versionRequested" }    ]  }]
复制代码


对于有很多选项的工具来说,这样很快就会变得非常繁琐,但幸运的是,我们并不需要手工来完成这项任务。

Picocli 注解处理器

picocli-codegen模块包含了一个注解处理器,它能够根据 picocli 注解在编译期就构建好一个模型,而不是在运行期。


在编译时,注解处理器会在META-INF/native-image/picocli-generated/$project目录生成 Graal 配置文件,它们会被包含在应用的 jar 中。其中,就包括 反射资源动态代理的配置文件。通过嵌入这些配置文件,我们的 jar 马上就能支持 Graal 了。在生成原生镜像时,大多数情况都不需要额外的配置了。


除此之外,注解处理器还会在编译期立即将非法注解或属性的错误展现出来,而不必等到运行时的测试阶段,这样会形成更短的反馈周期。


所以,我们需要做的就是使用类路径下的picocli-codegen来编译CheckSum.java源文件:


在 Linux 下,编译CheckSum.java并创建一个checksum.jar。在 Windows 下,需要将这些命令的:路径分隔符替换为;


mkdir classesjavac -cp .:picocli-4.2.0.jar:picocli-codegen-4.2.0.jar -d classes CheckSum.javacd classes && jar -cvef CheckSum ../checksum.jar * && cd ..
复制代码


在 jar 文件的META-INF/native-image/picocli-generated/目录下,我们会看到生成的配置文件:


jar -tf checksum.jarMETA-INF/META-INF/MANIFEST.MFCheckSum.classMETA-INF/native-image/META-INF/native-image/picocli-generated/META-INF/native-image/picocli-generated/proxy-config.jsonMETA-INF/native-image/picocli-generated/reflect-config.jsonMETA-INF/native-image/picocli-generated/resource-config.json
复制代码


我们的应用已经编写完成了。下一步,我们要制作一个原生镜像。

GraalVM 原生镜像工具链

要创建原生镜像,我们需要安装 GraalVM,确保已安装native-image工具,并根据构建所使用的 OS 安装 C/C++编译器工具链。在这个过程中,我遇到了一些问题,希望下面的步骤能够帮助其他开发人员解决相关的问题。


开发就是 10%的灵感加上 90%的环境搭建。

— 未知开发人员

安装 GraalVM

首先,安装最新版本的 GraalVM,在编写本文的时候,版本为 20.0。GraalVM 的起步指南页面是掌握在各种操作系统和容器下安装 GraalVM 最新指令的最佳地点。

安装原生镜像工具

GraalVM 附带了一个native-image生成器工具。在最近版本的 GraalVM 中,它需要预先下载并通过Graal Updater工具单独进行安装:


在 Linux 上为 Java 11 安装native-image生成器工具。


gu install -L /path/to/native-image-installable-svm-java11-linux-amd64-20.0.0.jar
复制代码


从 20.0 版本开始,对于 Windows 版本的 GraalVM 来说,这同样是必要的。


关于更多细节,请参见 GraalVM 的参考指南原生镜像章节。

安装编译器工具链

Linux 和 MacOS 编译器工具链

native-image的编译依赖于本地工具链,所以在 Linux 和 MacOS 上,我们需要对应操作系统上可用的glibc-develzlib-devel(C 库和 zlib 的头文件)和 gcc。


为了在 Linux 完成安装,需要执行 sudo dnf install gcc glibc-devel zlib-develsudo apt-get install build-essential libz-dev


在 macOS 上,需要执行xcode-select --install

针对 Java 8 的 Windows 编译器工具链

从 19.2.0 版本开始,GraalVM 开始为 Windows 原生镜像提供了实验性的支持。


对 Windows 的支持依然是实验性的,官方文档对 Windows 原生镜像的详细信息很少。从 19.3 版本开始,GraalVM 同时支持 Java 8 和 Java 11,在 Windows 上,它们需要不同的工具链。


要使用 Java 8 版本的 GraalVM 构建原生镜像,我们需要Microsoft Windows SDK for Windows 7和.NET Framework 4,以及来自KB2519277的C编译器。我们可以使用chocolatey来安装它们:


choco install windows-sdk-7.1 kb2519277
复制代码


然后(在 cmd 提示行中),激活 sdk-7.1 环境:


call "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd"
复制代码


这首先会启动一个新的命令提示行,并启用了 sdk-7.1 环境,在这个命令行提示窗口中运行所有后续的命令。这适用于 Java 8 环境下从 19.2.0 到 20.0 版本的所有 GraalVM。

针对 Java 11 的 Windows 编译器工具链

要使用 Java 11 版本的 GraalVM(19.3.0 及以上),我们可以要么安装 Visual Studio 2017 IDE(确保要包含 Visual C++ tools for CMake),要么可以通过chocolatey安装 Visual C Build Tools Workload for Visual Studio 2017 Build Tools:


choco install visualstudio2017-workload-vctools
复制代码


安装之后,在命令提示行中通过如下的命令搭建环境:


call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat"
复制代码


提示:


如果你安装了 Visual Studio 2017 IDE,那么需要在上面的命令中将BuildTools替换为CommunityEnterprise,这取决于你的 Visual Studio 版本。


然后,在命令行提示窗口中运行native-image

创建原生镜像

native-image 工具可以将 Java 应用编译为原生镜像,在进行编译的平台上,该原生镜像可以作为原生可执行文件来运行。在 Linux 上,如下所示:


在 Linux 上创建原生镜像


$ /usr/lib/jvm/graalvm/bin/native-image \    -cp classes:picocli-4.2.0.jar --no-server \    --static -H:Name=checksum  CheckSum
复制代码


在我的笔记本电脑上,native-image 会耗费大约一分钟的时间,并产生如下所示的输出:


[checksum:1073]    classlist:   3,124.74 ms,  1.14 GB[checksum:1073]        (cap):   2,885.31 ms,  1.14 GB[checksum:1073]        setup:   4,767.19 ms,  1.14 GB[checksum:1073]   (typeflow):   8,733.59 ms,  1.94 GB[checksum:1073]    (objects):   6,073.44 ms,  1.94 GB[checksum:1073]   (features):     313.28 ms,  1.94 GB[checksum:1073]     analysis:  15,384.41 ms,  1.94 GB[checksum:1073]     (clinit):     322.84 ms,  1.94 GB[checksum:1073]     universe:     793.02 ms,  1.94 GB[checksum:1073]      (parse):   2,191.69 ms,  1.94 GB[checksum:1073]     (inline):   2,064.62 ms,  2.13 GB[checksum:1073]    (compile):  14,960.43 ms,  2.73 GB[checksum:1073]      compile:  20,040.78 ms,  2.73 GB[checksum:1073]        image:   1,272.17 ms,  2.73 GB[checksum:1073]        write:     722.20 ms,  2.73 GB[checksum:1073]      [total]:  46,743.28 ms,  2.73 GB
复制代码


最终,我们会得到一个原生 Linux 可执行文件。有趣的是,Java 11 版本的 GraalVM 所创建的原生二进制文件要比 Java 8 版本的 GraalVM 所创建的文件更大一些:


-rwxrwxrwx 1 remko remko 14744296 Feb 19 09:51 java11-20.0/checksum*-rwxrwxrwx 1 remko remko 12393600 Feb 19 09:48 java8-20.0/checksum*
复制代码


我们可以看到文件是 12.4MB 和 14.7MB。到底文件是大还是小,则取决于我们要和什么进行对比。对我来讲,这种大小的文件是可以接受的。


接下来,我们运行该应用,确保它是可用的。在这个过程中,我们还可以对比正常基于 JIT 的 JVM 与原生镜像的启动时间:


$ time java -cp classes:picocli-4.2.0.jar CheckSum hi.txt764efa883dda1e11db47671c4a3bbd9ereal    0m0.415s   ← startup is 415 millis with normal Javauser    0m0.609ssys     0m0.313s$ time ./checksum hi.txt764efa883dda1e11db47671c4a3bbd9ereal    0m0.004s   ← native image starts up in 4 millisuser    0m0.002ssys     0m0.002s
复制代码


至少在 Linux 上我们已经看到了如何将 Java 应用分发为单个原生可执行文件。那在 Windows 上又会怎样呢?

Windows 上的原生镜像

原生镜像对 Windows 的支持还有一些缺陷,所以我们会对此进行更详细的讨论。

在 Windows 上创建原生镜像

创建原生镜像本身并不是什么问题。举例来讲:


在 Windows 上创建原生镜像


C:\apps\graalvm-ce-java8-20.0.0\bin\native-image ^    -cp picocli-4.2.0.jar --static -jar checksum.jar
复制代码


在 Windows 上,native-image.cmd 工具的输出和 Linux 类似,耗费的时间大致相当,所生成的可执行文件稍微小一些,Java 8 版本的 GraalVM 对应的输出是 11.3MB,Java 11 版本的 GraalVM 所输出的二进制文件是 14.2MB。


二进制文件能够很好地运行,但是有一个差异:在控制台我们没有看到 ANSI 的颜色,接下来,看一下如何修复它。

具有彩色输出的 Windows 原生镜像

为了在 Windows 命令提示行中实现 ANSI 彩色显示,我们需要使用Jansi库。但令人遗憾的是,Jansi(从 1.18 版本开始)有一些问题,这意味着在 GraalVM 原生镜像中,它无法产生彩色的输出。为了解决该问题,picocli 提供了一个Jansi协作库,即picocli-jansi-graalvm,它能够让 Jansi 库在 Windows 的 GraalVM 原生镜像上正确地运行。


我们要修改main方法,告诉 Jansi 在 Windows 上启用渲染 ANSI 转义码的功能,如下所示:


    //...import picocli.jansi.graalvm.AnsiConsole;//...public class CheckSum implements Callable<Integer> {  // ...  public static void main(String[] args) {    int exitCode = 0;    // enable colors on Windows    try (AnsiConsole ansi = AnsiConsole.windowsInstall()) {      exitCode = new CommandLine(new CheckSum()).execute(args);    }    System.exit(exitCode);  }}
复制代码


通过该命令构建新的原生镜像(注意,从 GraalVM 19.3 开始,我们需要在类路径中引用 jar 文件):


set GRAALVM_HOME=C:\apps\graalvm-ce-java11-20.0.0%GRAALVM_HOME%\bin\native-image ^  -cp "picocli-4.2.0.jar;jansi-1.18.jar;picocli-jansi-graalvm-1.1.0.jar;checksum.jar" ^  picocli.nativecli.demo.CheckSum checksum
复制代码


在 DOS 控制台应用中,我们就能看到彩色的输出的了:



这需要额外多花点功夫,但是现在我们的原生 Windows CLI 应用可以使用彩色对比了,提供了与 Linux 类似的用户体验。


添加 Jansi 库对形成的二进制文件的大小并没有什么变化:使用 Java 11 GraalVM 构建的二进制文件是 14.3MB,使用 Java 8 GraalVM 构建的二进制文件是 11.3MB。

在 Windows 上运行原生镜像

我们几乎就要完工了,但是还有一个问题是尚未暴露的。


我们刚才创建的二进制文件在构建它的机器上能够很好地运行,但是如果我们在另外一台 Windows 机器上运行的话,你可能会看到如下的错误:



它表明,我们的原生镜像需要来自 VS C++ Redistributable 2010 的msvcr100.dll。这个 dll 文件可以放到与exe文件相同的目录下,也可以放到C:\Windows\System32中。有一项正在进行中的工作在试图解决该问题。


在 Java 11 的 GraalVM 中,我们可以看到类似的错误,只不过它提示的是缺少不同的 DLL,即VCRUNTIME140.dll



就现在来讲,我们只能随应用一起分发这些 DLL,或者告诉用户下载并安装 Microsoft Visual C++ 2015 Redistributable Update 3 RC以便于获取基于 Java 11 的原生镜像所需的VCRUNTIME140.dll,或者安装Microsoft Visual C++ 2010 SP1 Redistributable Package (x64)以获取基于 Java 8 的原生镜像所需的msvcr100.dll


GraalVM 不支持交叉编译(cross-compilation),不过将来可能会支持。现在,我们需要在 Linux 上编译以获得 Linux 可执行文件,在 MacOS 上编译以获得 MacOS 可执行文件,在 Windows 上编译以获得 Windows 可执行文件。

结论

命令行应用程序是 GraalVM 原生镜像的典型使用场景:我们现在可以使用 Java(或另外的 JVM 语言)进行开发,并将 CLI 应用程序作为单个的、相对较小的原生可执行文件进行分发。(只不过在 Windows 上,我们可能需要分发一个额外的运行时 DLL。)最大的好处就是快速启动和减少内存占用。


GraalVM 原生镜像有一些限制,应用程序可能需要做一些工作才能转换成原生镜像。


Picocli使得借助众多 JVM 语言编写命令行应用程序变得很容易,并且提供了一些附加功能,可以轻松地将 CLI 应用程序转换为原生镜像。


在你的下一个命令行应用程序中,尝试一下 Picocli 和 GraalVM 吧!


作者介绍:


Remko Popma 白天在 SMBC Nikko Securities 做 Algo 开发,晚上则是picocli的作者。Popma 也是 Log4j 2 的性能黑客。他使 Log4j 2 免受垃圾回收之苦,并贡献了 Async Loggers,与当前市场领先的日志包 log4j-1.x 和 logback 相比,Async Loggers 在多线程场景中具有 10 倍的吞吐量和多个数量级的低延迟。Popma 负责了 Log4j 2.0-beta4 版本以来大部分的性能改进。出于兴趣,他喜欢学习新东西:性能、并发性、可伸缩性、低延迟、人工智能、机器学习、密码学、比特币和加密货币技术。你可以通过 @RemkoPopma 找到 Popma。


原文链接:


Build Great Native CLI Apps in Java with Graalvm and Picocli


2020-03-26 16:254241

评论 1 条评论

发布
用户头像
tks
2020-08-05 10:14
回复
没有更多了
发现更多内容

微信亿级用户异常检测框架的设计与实践

OpenIM

五行兼备:联想TruScale服务的太极之道

脑极体

我怀疑,你对996的力量一无所知!

艾小仙

程序员 996

北鲲云超算平台赋能蛋白设计助推生物制药行业发展

北鲲云

谁在制造“完美男性”?

脑极体

Java中对千万级数据量的表进行插入操作(MYSQL)

张音乐

Java MySQL JDBC 9月日更

maven-dependency中作用域scope含义

一个大红包

9月日更

全网最新最全面Java程序员面试清单(12专题5000解析)

Java 架构 面试 程序人生 程序

网络攻防学习笔记 Day131

穿过生命散发芬芳

网络安全 9月日更

数字技术重构产业链供应链比较优势

CECBC

活动推荐 | 云原生社区 Meetup 第七期深圳站开始报名啦!

CODING DevOps

Kubernetes DevOps 微服务 活动 Meetup

总结下ThinkPHP的代码审计方法

网络安全学海

php 网络安全 信息安全 WEB安全 代码审计

低代码开发:实现传统系统信息化的3种方案!

优秀

低代码 低代码开发

垃圾分类与AI的反碎片之旅

百度大脑

人工智能 EasyDL

深入理解rtmp(一)之开发环境搭建

轻口味

android 音视频 直播 9月日更

Python代码阅读(第25篇):将多行字符串拆分成列表

Felix

编程 Code Programing 阅读代码 -python

用数据搭建反馈系统

石云升

数据分析 9月日更

防沉迷系统的bug,技术如何查漏补缺?

脑极体

新鲜出炉!腾讯3轮面试,拿53k*15offer全仰仗这份Java面试神技

Java 编程 架构 面试 程序人生

前端独立交付需求背景下的Mock数据多方案解读

爱数技术范儿

JavaScript 大前端 Mock

Java 操作 Office:POI word 之表格格式

程序员架构进阶

Java Apache POI 9月日更 word文档

Vue进阶(幺零四):elementUI 应用 $notify 提示信息中换行问题

No Silver Bullet

Vue 9月日更

Frida笔记 - Android 篇 (一)

GrowingIO技术专栏

android Frida

性能测试中异步展示测试进度

FunTester

性能测试 接口测试 测试框架 进度条 FunTester

每个人都在谈数据治理,每个人都治理不好

奔向架构师

数据仓库 数据治理 9月日更

HTML进阶

Augus

html 9月日更

云南推进“区块链+数据中心”融合发展

CECBC

开源之夏项目分享:图数据库 Nebula Graph 支持 JDBC 协议

NebulaGraph

ULP Fec与 Flex FEC 概述

webrtc developer

WebRTC fec

ServiceWorker工作原理、生命周期和使用场景

devpoint

Service Worker 9月日更

Python顺序结构选择结构

在即

9月日更

  • 需要帮助,请添加网站小助手,进入 InfoQ 技术交流群
如何借助Graalvm和Picocli构建Java编写的原生CLI应用_编程语言_Remko Popma_InfoQ精选文章