Scripting with Quarkus

Quarkus 提供与 jbang 的集成,允许你编写 Java 脚本/应用程序,无需 Maven 或 Gradle 便能运行。 在本指南中,我们将了解如何仅使用一个 Java 文件编写 REST 应用程序。 :iokays-category: quarkus :iokays-path: modules/ROOT/pages/_includes/extension-status.adoc :keywords: Quarkus, 中文文档, 编程技术

该技术被认为是 {extension-status}。 有关可能状态的完整列表,请查看我们的 FAQ entry.

Prerequisites

如要完成本指南,您需要:

  • Roughly 15 minutes

  • An IDE

  • 安装了 JDK 17+,已正确配置 JAVA_HOME

  • Apache Maven ${proposed-maven-version}

  • 如果你想使用 Quarkus CLI, 则可以选择使用

  • 如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately

Solution

我们通常会链接到 Git 存储库以便克隆,但在这种情况下,除了以下文件以外,没有其他文件:

//usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS {quarkus-platform-groupid}:quarkus-bom:{quarkus-version}@pom
//DEPS io.quarkus:quarkus-rest
//JAVAC_OPTIONS -parameters
//JAVA_OPTIONS -Djava.util.logging.manager=org.jboss.logmanager.LogManager

import io.quarkus.runtime.Quarkus;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/hello")
@ApplicationScoped
public class quarkusapp {

    @GET
    public String sayHello() {
        return "hello";
    }

    public static void main(String[] args) {
        Quarkus.run(args);
    }

    @Inject
    GreetingService service;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/greeting/{name}")
    public String greeting(String name) {
        return service.greeting(name);
    }

    @ApplicationScoped
    static public class GreetingService {

        public String greeting(String name) {
            return "hello " + name;
        }
    }
}

Architecture

在本指南中,我们使用单个源文件创建一个简单的应用程序,该应用程序提供一个 hello 端点,无需 pom.xmlbuild.gradle 等其他构建文件。为了演示依赖注入,此端点使用 greeting bean。

getting started architecture

Creating the initial file

首先,我们需要一个 Java 文件。JBang 允许你使用以下命令创建一个初始版本:

jbang init scripting/quarkusapp.java
cd scripting

此命令会生成一个 .java 文件,你可以在 Linux 和 macOS 中直接运行它,即 ./quarkusapp.java,在 Windows 中你需要使用 jbang quarkusapp.java

此初始版本运行后将打印 Hello World

生成后,查看 quarkusapp.java 文件。

你会在顶部找到一行类似于此的内容:

//usr/bin/env jbang "$0" "$@" ; exit $?

在 Linux 和 macOS 中,此行允许你将其作为一个脚本运行。在 Windows 中,此行为被忽略。

接下来的几行

//DEPS <dependency1> <dependency2>

说明了你如何向这个脚本添加依赖项。这是 JBang 的一项功能。

继续并更新此行以包括 quarkus-bomquarkus-rest 依赖项,如下所示:

//DEPS {quarkus-platform-groupid}:quarkus-bom:{quarkus-version}@pom
//DEPS io.quarkus:quarkus-rest

现在,运行 jbang quarkusapp.java,你将看到 JBang 解析此依赖项并借助 Quarkus 的 JBang 集成构建 jar。

$ jbang quarkusapp.java

[jbang] Resolving dependencies...
[jbang]     Resolving io.quarkus:quarkus-resteasy:{quarkus-version}...Done
[jbang] Dependencies resolved
[jbang] Building jar...
[jbang] Post build with io.quarkus.launcher.JBangIntegration
Mar 22, 2023 9:47:51 A.M. org.jboss.threads.Version <clinit>
INFO: JBoss Threads version 3.5.0.Final
Mar 22, 2023 9:47:51 A.M. io.quarkus.deployment.QuarkusAugmentor run
INFO: Quarkus augmentation completed in 722ms
Hello World

目前,应用程序没有新的功能。

How do I edit this file and get content assist?

如需在带有内容辅助的 IDE/编辑器中编辑 JBang 脚本,你可以运行 jbang edit quarkusapp.javajbang edit quarkusapp.java。 有关更多信息,请参阅 the JBang documentation

The Jakarta REST resources

下面让我们用一个使用 Quarkus 特性的类替换之前的类:

import io.quarkus.runtime.Quarkus;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("/hello")
@ApplicationScoped
public class quarkusapp {

    @GET
    public String sayHello() {
        return "hello";
    }

    public static void main(String[] args) {
        Quarkus.run(args);
    }
}

这是一个非常简单的类,它有一个 main 方法,它用 REST 端点启动 Quarkus,并对 "/hello" 上的请求返回 "hello"。

Why is the main method there?

一个 main 方法对于 JBang 集成当前是必需的 - 我们可能会在未来移除此需求。

Running the application

现在,当你运行应用程序时,你会看到 Quarkus 启动。

使用: jbang quarkusapp.java

$ jbang quarkusapp.java

[jbang] Building jar...
[jbang] Post build with io.quarkus.launcher.JBangIntegration
Mar 22, 2023 9:48:39 A.M. org.jboss.threads.Version <clinit>
INFO: JBoss Threads version 3.5.0.Final
Mar 22, 2023 9:48:39 A.M. io.quarkus.deployment.QuarkusAugmentor run
INFO: Quarkus augmentation completed in 521ms
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2023-03-22 09:48:39,891 INFO  [io.quarkus] (main) quarkus 999-SNAPSHOT on JVM (powered by Quarkus {quarkus-version}) started in 0.283s. Listening on: http://0.0.0.0:8080
2023-03-22 09:48:39,904 INFO  [io.quarkus] (main) Profile prod activated.
2023-03-22 09:48:39,904 INFO  [io.quarkus] (main) Installed features: [cdi, rest, smallrye-context-propagation, vertx]

一旦启动,你可以请求提供的端点:

$ curl -w "\n" http://localhost:8080/hello
hello

之后,点击 CTRL+C 停止应用程序。

Automatically add newline with curl -w "\n"

我们在本例中使用 `curl -w "\n"`是为了避免你的终端打印一个 '%' 或把结果和下一个命令提示放在同一行。

Why is quarkus-rest not resolved?

在第二次运行中,你应该不会看到一行话,说正在解决 quarkus-rest,因为 JBang 在两次运行之间缓存依赖项解析。如果你想清除缓存以强制解析,请使用 jbang cache clear

Using injection

Quarkus 中的依赖注入基于 ArC,ArC 是一种基于 CDI 的依赖注入解决方案,专为 Quarkus 的架构定制。你可以在 Contexts and Dependency Injection guide 中了解更多信息。

ArC 作为 quarkus-rest 的依赖项附带提供,所以你已经手边有它了。

让我们修改应用程序并添加一个伴随 Bean。

通常情况下,你将添加一个单独的类,但由于我们的目标是将其全部放在一个文件中,你将添加一个嵌套类。

quarkusapp 类体中添加以下 inside

@ApplicationScoped
static public class GreetingService {

    public String greeting(String name) {
        return "hello " + name;
    }

}
Use of nested static public classes

出于两个原因,我们使用嵌套静态 public 类而不是顶级类:

  1. JBang 目前不支持多个源文件。

  2. 所有依赖于内省的 Java 框架在使用顶级类时会遇到挑战,因为它们不像 public 类那样明显;并且在 Java 中,一个文件中只能有一个顶级 public 类。

编辑 quarksapp 类以注入 GreetingService,并使用它创建一个新的端点,你应该最终得到如下内容:

//usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS io.quarkus.platform:quarkus-bom:{quarkus-version}@pom
//DEPS io.quarkus:quarkus-rest

import io.quarkus.runtime.Quarkus;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/hello")
@ApplicationScoped
public class quarkusapp {

    @GET
    public String sayHello() {
        return "hello";
    }

    public static void main(String[] args) {
        Quarkus.run(args);
    }

    @Inject
    GreetingService service;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/greeting/{name}")
    public String greeting(String name) {
        return service.greeting(name);
    }

    @ApplicationScoped
    static public class GreetingService {

        public String greeting(String name) {
            return "hello " + name;
        }
    }
}

现在,当您运行 jbang quarkusapp.java 时,您可以检查新端点的返回内容:

$ curl -w "\n" http://localhost:8080/hello/greeting/quarkus
hello null

这是预料之外的,为什么它会返回 hello null 而不是 hello quarkus

原因是 Quarkus REST(前身为 RESTEasy Reactive)依赖于 -parameters 编译器标志,以能够将 {name} 映射到 name 参数。

我们通过将以下注释指令添加到文件中来修复它:

//JAVAC_OPTIONS -parameters

现在,当您使用 jbang quarkusapp.java 运行时,端点应该返回您期望的内容:

$ curl -w "\n" http://localhost:8080/hello/greeting/quarkus
hello quarkus

Debugging

要调试应用程序,您使用 jbang --debug quarkusapp.java,并且可以使用您的 IDE 连接到端口 4004;如果您希望使用更传统的 Quarkus 调试端口,则可以使用 jbang --debug=5005 quarkusapp.java

注意:JBang 调试始终暂停,因此您需要连接调试器才能运行应用程序。

Logging

要使用 JBang 在 Quarkus 脚本中进行日志记录,您需要像往常一样配置一个记录器,即:

public static final Logger LOG = Logger.getLogger(quarkusapp.class);

为了使其正常工作,您需要添加一个 Java 选项以确保日志记录正确初始化,即:

//JAVA_OPTIONS -Djava.util.logging.manager=org.jboss.logmanager.LogManager

现在运行 jbang quarkusapp.java,日志和渲染将按预期进行。

Configuring Application

要配置应用程序,您通常可以使用 application.properties 文件,但您需要将其 add 到脚本中:

//FILES application.properties

// ...
@ConfigProperty(name = "prefix", defaultValue = "WG -")
String prefix;

这将使 application.properties 文件可用于脚本,并像往常一样处理配置。

您还可以使用 application.yaml 文件。为此,您需要将其 addapplication.yaml 文件到脚本中,并包含 quarkus-config-yaml 依赖项:

//DEPS io.quarkus:quarkus-config-yaml
//FILES application.yaml

application.propertiesapplication.yaml 文件的路径相对于脚本文件。

Running as a native application

如果您已安装 native-image 二进制文件并设置了 GRAALVM_HOME,或在 Linux 中安装了容器运行时(例如 podman 或 docker),则可以使用 jbang --native quarkusapp.java 获取本机可执行文件并运行:

$ jbang --native quarkusapp.java
[jbang] Building jar...
[jbang] Post build with io.quarkus.launcher.JBangIntegration
Mar 22, 2023 9:58:47 A.M. org.jboss.threads.Version <clinit>
INFO: JBoss Threads version 3.5.0.Final
Mar 22, 2023 9:58:47 A.M. io.quarkus.deployment.pkg.steps.JarResultBuildStep buildNativeImageThinJar
INFO: Building native image source jar: /tmp/quarkus-jbang8082065952748314720/quarkus-application-native-image-source-jar/quarkus-application-runner.jar
Mar 22, 2023 9:58:47 A.M. io.quarkus.deployment.pkg.steps.NativeImageBuildStep build
INFO: Building native image from /tmp/quarkus-jbang8082065952748314720/quarkus-application-native-image-source-jar/quarkus-application-runner.jar
Mar 22, 2023 9:58:47 A.M. io.quarkus.deployment.pkg.steps.NativeImageBuildStep getNativeImageBuildRunner
WARN: Cannot find the `native-image` in the GRAALVM_HOME, JAVA_HOME and System PATH. Install it using `gu install native-image` Attempting to fall back to container build.
Mar 22, 2023 9:58:47 A.M. io.quarkus.deployment.pkg.steps.NativeImageBuildContainerRunner <init>
INFO: Using docker to run the native image builder
Mar 22, 2023 9:58:47 A.M. io.quarkus.deployment.pkg.steps.NativeImageBuildContainerRunner setup
INFO: Checking image status quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.3-java17
Mar 22, 2023 9:58:51 A.M. io.quarkus.deployment.pkg.steps.NativeImageBuildStep checkGraalVMVersion
INFO: Running Quarkus native-image plugin on native-image 22.3.1.0-Final Mandrel Distribution (Java Version 17.0.6+10)
Mar 22, 2023 9:58:51 A.M. io.quarkus.deployment.pkg.steps.NativeImageBuildRunner build
INFO: docker run --env LANG=C --rm --user 1000:1000 -v /tmp/quarkus-jbang8082065952748314720/quarkus-application-native-image-source-jar:/project:z --name build-native-XaZUc quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.3-java17 -J-Dsun.nio.ch.maxUpdateArraySize=100 -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -J-Dlogging.initial-configurator.min-level=500 -J-Dvertx.logger-delegate-factory-class-name=io.quarkus.vertx.core.runtime.VertxLogDelegateFactory -J-Dvertx.disableDnsResolver=true -J-Dio.netty.noUnsafe=true -J-Dio.netty.leakDetection.level=DISABLED -J-Dio.netty.allocator.maxOrder=3 -J-Duser.language=en -J-Duser.country=IE -J-Dfile.encoding=UTF-8 --features=io.quarkus.runner.Feature,io.quarkus.runtime.graal.DisableLoggingFeature -J--add-exports=java.security.jgss/sun.security.krb5=ALL-UNNAMED -J--add-opens=java.base/java.text=ALL-UNNAMED -J--add-opens=java.base/java.io=ALL-UNNAMED -J--add-opens=java.base/java.lang.invoke=ALL-UNNAMED -J--add-opens=java.base/java.util=ALL-UNNAMED -H:+CollectImageBuildStatistics -H:ImageBuildStatisticsFile=quarkus-application-runner-timing-stats.json -H:BuildOutputJSONFile=quarkus-application-runner-build-output-stats.json -H:+AllowFoldMethods -J-Djava.awt.headless=true --no-fallback --link-at-build-time -H:+ReportExceptionStackTraces -H:-AddAllCharsets --enable-url-protocols=http -H:NativeLinkerOption=-no-pie -H:-UseServiceLoaderFeature -H:+StackTrace -J--add-exports=org.graalvm.sdk/org.graalvm.nativeimage.impl=ALL-UNNAMED --exclude-config io\.netty\.netty-codec /META-INF/native-image/io\.netty/netty-codec/generated/handlers/reflect-config\.json --exclude-config io\.netty\.netty-handler /META-INF/native-image/io\.netty/netty-handler/generated/handlers/reflect-config\.json quarkus-application-runner -jar quarkus-application-runner.jar
Mar 22, 2023 9:37:56 A.M. io.quarkus.deployment.pkg.steps.NativeImageBuildRunner runCommand
INFO: docker run --env LANG=C --rm --user 1000:1000 -v /tmp/quarkus-jbang9315448339582904220/quarkus-application-native-image-source-jar:/project:z --entrypoint /bin/bash quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.3-java17 -c objcopy --strip-debug quarkus-application-runner
Mar 22, 2023 9:37:57 A.M. io.quarkus.deployment.QuarkusAugmentor run
INFO: Quarkus augmentation completed in 31729ms
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2023-03-22 09:37:57,471 INFO  [io.quarkus] (main) quarkus 999-SNAPSHOT native (powered by {quarkus-version}) started in 0.009s. Listening on: http://0.0.0.0:8080
2023-03-22 09:37:57,472 INFO  [io.quarkus] (main) Profile prod activated.
2023-03-22 09:37:57,472 INFO  [io.quarkus] (main) Installed features: [cdi, rest, smallrye-context-propagation, vertx]

在本机构建中,首次运行需要一些时间,但后续运行(不更改 quarkusapp.java)将得益于 JBang 缓存而几乎是即时的:

$ jbang --native quarkusapp.java
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2023-03-22 09:38:45,450 INFO  [io.quarkus] (main) quarkus 999-SNAPSHOT native (powered by {quarkus-version}) started in 0.009s. Listening on: http://0.0.0.0:8080
2023-03-22 09:38:45,450 INFO  [io.quarkus] (main) Profile prod activated.
2023-03-22 09:38:45,450 INFO  [io.quarkus] (main) Installed features: [cdi, rest, smallrye-context-propagation, vertx]

Using Qute

您可以在 JBang 脚本中使用 Qute templating engine,方法是添加 quarkus-qute 依赖项。您还需要在脚本中包含 templates 目录:

//DEPS io.quarkus:quarkus-qute
//FILES templates/=templates/*

// ...

 @Inject
 Template template; // Locate and load the `templates/template.html` file

如果您的 templates 目录包含子目录,请使用 templates/=templates/*/ 代替。

Conclusion

如果您想开始使用 Quarkus 或快速编写一些内容,那么使用 jbang 的 Quarkus 脚本可让您做到这一点。无需 Maven,无需 Gradle - 只需一个 Java 文件。在本指南中,我们概述了使用 JBang 的 Quarkus 的基本知识;如果您想详细了解 JBang 的功能,请参阅 [role="bare"][role="bare"]https://jbang.dev。