Scripting with Quarkus

Quarkus 提供与 jbang 的集成,允许你编写 Java 脚本/应用程序,无需 Maven 或 Gradle 便能运行。

Quarkus provides integration with jbang which allows you to write Java scripts/applications requiring no Maven nor Gradle to get running.

在本指南中,我们将了解如何仅使用一个 Java 文件编写 REST 应用程序。

In this guide, we will see how you can write a REST application using just a single Java file. Unresolved directive in scripting.adoc - include::{includes}/extension-status.adoc[]

Prerequisites

Unresolved directive in scripting.adoc - include::{includes}/prerequisites.adoc[]* JBang

Unresolved directive in scripting.adoc - include::{includes}/prerequisites.adoc[] * JBang

Solution

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

Normally we would link to a Git repository to clone but in this case there is no additional files than the following:

//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。

In this guide, we create a straightforward application serving a hello endpoint with a single source file, no additional build files like pom.xml or build.gradle needed. To demonstrate dependency injection, this endpoint uses a greeting bean.

getting started architecture

Creating the initial file

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

First, we need a Java file. JBang lets you create an initial version using:

jbang init scripting/quarkusapp.java
cd scripting

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

This command generates a .java file that you can directly run on Linux and macOS, i.e. ./quarkusapp.java - on Windows you need to use jbang quarkusapp.java.

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

This initial version will print Hello World when run.

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

Once generated, look at the quarkusapp.java file.

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

You will find at the top a line looking like this:

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

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

This line is what on Linux and macOS allows you to run it as a script. On Windows this line is ignored.

接下来的几行

The next lines

//DEPS <dependency1> <dependency2>

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

illustrate how you add dependencies to this script. This is a feature of JBang.

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

Go ahead and update this line to include the quarkus-bom and the quarkus-rest dependency like so:

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

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

Now, run jbang quarkusapp.java and you will see JBang resolving this dependency and building the jar with help from Quarkus' JBang integration.

$ 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

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

For now the application does nothing new.

How do I edit this file and get content assist?

如需在带有内容辅助的 IDE/编辑器中编辑 JBang 脚本,你可以运行 jbang edit quarkusapp.javajbang edit quarkusapp.java

To edit the JBang script in an IDE/editor with content assist you can run jbang edit quarkusapp.java or jbang edit quarkusapp.java.

有关更多信息,请参阅 the JBang documentation

For more information please refer to the the JBang documentation.

The Jakarta REST resources

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

Now let us replace the class with one that uses Quarkus features:

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"。

It’s a very simple class with a main method that starts Quarkus with a REST endpoint, returning "hello" to requests on "/hello".

Why is the main method there?

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

A main method is currently needed for the JBang integration to work - we might remove this requirement in the future.

Running the application

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

Now when you run the application you will see Quarkus start up.

使用: jbang quarkusapp.java

Use: 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]

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

Once started, you can request the provided endpoint:

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

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

After that, hit CTRL+C to stop the application.

Automatically add newline with curl -w "\n"

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

We are using curl -w "\n" in this example to avoid your terminal printing a '%' or put both result and next command prompt on the same line.

Why is quarkus-rest not resolved?

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

In this second run you should not see a line saying it is resolving quarkus-rest as JBang caches the dependency resolution between runs. If you want to clear the caches to force resolution use jbang cache clear.

Using injection

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

Dependency injection in Quarkus is based on ArC which is a CDI-based dependency injection solution tailored for Quarkus' architecture. You can learn more about it in the Contexts and Dependency Injection guide.

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

ArC comes as a dependency of quarkus-rest so you already have it handy.

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

Let’s modify the application and add a companion bean.

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

Normally you would add a separate class, but as we are aiming to have it all in one file you will add a nested class.

quarkusapp 类体中添加以下 inside

Add the following inside the quarkusapp class body.

@ApplicationScoped
static public class GreetingService {

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

}
Use of nested static public classes

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

We are using a nested static public class instead of a top level class for two reasons:

  1. JBang currently does not support multiple source files.

  2. All Java frameworks relying on introspection have challenges using top level classes as they are not as visible as public classes; and in Java there can only be one top level public class in a file.

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

Edit the quarksapp class to inject the GreetingService and create a new endpoint using it, you should end up with something like:

//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 时,您可以检查新端点的返回内容:

Now when you run jbang quarkusapp.java you can check what the new end point returns:

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

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

Now that is unexpected, why is it returning hello null and not hello quarkus?

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

The reason is that Quarkus REST (formerly RESTEasy Reactive) relies on the -parameters compiler flag to be set to be able to map {name} to the name parameter.

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

We fix that by adding the following comment instruction to the file:

//JAVAC_OPTIONS -parameters

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

Now when you run with jbang quarkusapp.java the end point should return what you expect:

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

Debugging

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

To debug the application you use jbang --debug quarkusapp.java and you can use your IDE to connect on port 4004; if you want to use the more traditional Quarkus debug port you can use jbang --debug=5005 quarkusapp.java.

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

Note: JBang debugging always suspends thus you need to connect the debugger to have the application run.

Logging

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

To use logging in Quarkus scripting with JBang you do as usual, with configuring a logger, i.e.

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

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

To get it to work you need to add a Java option to ensure the logging is initialized properly, i.e.

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

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

With that in place running jbang quarkusapp.java will log and render as expected.

Configuring Application

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

To configure the application you can use the application.properties file as usual, but you need to add it to the script:

//FILES application.properties

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

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

This will make the application.properties file available to the script, and process the configuration as usual.

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

You can also use the application.yaml file. For this, you need to add it to the application.yaml file to the script and include the quarkus-config-yaml dependency:

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

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

The path to the application.properties and application.yaml files are relative to the script file.

Running as a native application

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

If you have the native-image binary installed and GRAALVM_HOME set, or a container runtime (e.g., podman or docker) installed on Linux, you can get the native executable built and run using 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 缓存而几乎是即时的:

This native build will take some time on first run but any subsequent runs (without changing quarkusapp.java) will be close to instant thanks to JBang cache:

$ 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 目录:

You can use the Qute templating engine in your JBang script by adding the quarkus-qute dependency. You also need to include the templates directory in the script:

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

// ...

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

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

If your templates directory includes sub-directories, use templates/=templates/*/ instead.

Conclusion

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

If you want to get started with Quarkus or write something quickly, Quarkus Scripting with jbang lets you do that. No Maven, no Gradle - just a Java file. In this guide we outlined the very basics on using Quarkus with JBang; if you want to learn more about what JBang can do, go see [role="bare"]https://jbang.dev.