Qute Templating Engine

Qute 是专门为满足 Quarkus 需求而设计的模板引擎。尽可能减少反射的使用,以减小原生镜像的大小。此 API 同时结合了命令式以及非阻塞反应式编码样式。在开发模式下,观察 `src/main/resources/templates`中所有文件的更改,修改内容会立即显示出来。此外,我们尝试在构建时检测到大部分模板问题。在本指南中,您将了解如何在应用程序中轻松呈现模板。

Qute is a templating engine designed specifically to meet the Quarkus needs. The usage of reflection is minimized to reduce the size of native images. The API combines both the imperative and the non-blocking reactive style of coding. In the development mode, all files located in src/main/resources/templates are watched for changes and modifications are immediately visible. Furthermore, we try to detect most of the template problems at build time. In this guide, you will learn how to easily render templates in your application.

Solution

我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

克隆 Git 存储库: git clone {quickstarts-clone-url},或下载 {quickstarts-archive-url}[存档]。

Clone the Git repository: git clone {quickstarts-clone-url}, or download an {quickstarts-archive-url}[archive].

解决方案位于 qute-quickstart directory

The solution is located in the qute-quickstart directory.

Serving Qute templates via HTTP

如果您想要通过 HTTP 提供模板:

If you want to serve your templates via HTTP:

  1. The Qute Web extension allows you to directly serve via HTTP templates located in src/main/resources/templates/pub/. In that case you don’t need any Java code to "plug" the template, for example, the template src/main/resources/templates/pub/foo.html will be served from the paths /foo and /foo.html by default.

  2. For finer control, you can combine it with Quarkus REST to control how your template will be served. All files located in the src/main/resources/templates directory and its subdirectories are registered as templates and can be injected in a REST resource.

pom.xml
<dependency>
    <groupId>io.quarkiverse.qute.web</groupId>
    <artifactId>quarkus-qute-web</artifactId>
</dependency>
build.gradle
implementation("io.quarkiverse.qute.web:quarkus-qute-web")

Qute Web 扩展虽然托管在 Quarkiverse 中,但它是 Quarkus 平台的一部分,其版本在 Quarkus Platform BOM 中定义。

The Qute Web extension, while hosted in the Quarkiverse, is part of the Quarkus Platform and its version is defined in the Quarkus Platform BOM.

Serving Hello World with Qute

让我们从 Hello World 模板开始:

Let’s start with a Hello World template:

src/main/resources/templates/pub/hello.html
<h1>Hello {http:param('name', 'Quarkus')}!</h1> 1
1 {http:param('name', 'Quarkus')} is an expression that is evaluated when the template is rendered (Quarkus is the default value).

位于 `pub`目录中的模板是通过 HTTP 提供的。此行为是内置行为,不需要控制器。例如,模板 `src/main/resources/templates/pub/foo.html`将默认从路径 `/foo`和 `/foo.html`提供。

Templates located in the pub directory are served via HTTP. This behavior is built-in, no controllers are needed. For example, the template src/main/resources/templates/pub/foo.html will be served from the paths /foo and /foo.html by default.

如果您的应用程序正在运行,您可以打开浏览器并按 [role="bare"][role="bare"]http://localhost:8080/hello?name=Martin

If your application is running, you can open your browser and hit: [role="bare"]http://localhost:8080/hello?name=Martin

有关 Qute Web 选项的更多信息,请参见 Qute Web guide

For more information about Qute Web options, see the Qute Web guide.

Hello Qute and REST

为了进行更精细的控制,您可以将 Qute Web 与 Quarkus REST 或 Quarkus RESTEasy 结合使用,以控制如何提供您的模板

For finer control, you can combine Qute Web with Quarkus REST or Quarkus RESTEasy to control how your template will be served

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-rest")

一个非常简单的文本模板:

A very simple text template:

hello.txt
Hello {name}! 1
1 {name} is a value expression that is evaluated when the template is rendered.

现在让我们把“已编译”的模板注入到资源类中。

Now let’s inject the "compiled" template in the resource class.

HelloResource.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;

@Path("hello")
public class HelloResource {

    @Inject
    Template hello; 1

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return hello.data("name", name); 2 3
    }
}
1 If there is no @Location qualifier provided, the field name is used to locate the template. In this particular case, we’re injecting a template with path templates/hello.txt.
2 Template.data() returns a new template instance that can be customized before the actual rendering is triggered. In this case, we put the name value under the key name. The data map is accessible during rendering.
3 Note that we don’t trigger the rendering - this is done automatically by a special ContainerResponseFilter implementation.

如果你的应用程序正在运行,则可以请求端点:

If your application is running, you can request the endpoint:

$ curl -w "\n" http://localhost:8080/hello?name=Martin
Hello Martin!

Type-safe templates

在 Java 代码中声明模板的备用方法依赖于以下约定:

There’s an alternate way to declare your templates in your Java code, which relies on the following convention:

  • Organise your template files in the /src/main/resources/templates directory, by grouping them into one directory per resource class. So, if your ItemResource class references two templates hello and goodbye, place them at /src/main/resources/templates/ItemResource/hello.txt and /src/main/resources/templates/ItemResource/goodbye.txt. Grouping templates per resource class makes it easier to navigate to them.

  • In each of your resource class, declare a @CheckedTemplate static class Template {} class within your resource class.

  • Declare one public static native TemplateInstance method(); per template file for your resource.

  • Use those static methods to build your template instances.

以下是使用这种风格重写的先前示例:

Here’s the previous example, rewritten using this style:

我们将从一个非常简单的模板开始:

We’ll start with a very simple template:

HelloResource/hello.txt
Hello {name}! 1
1 {name} is a value expression that is evaluated when the template is rendered.

现在让我们在资源类中声明并使用这些模板。

Now let’s declare and use those templates in the resource class.

HelloResource.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.CheckedTemplate;

@Path("hello")
public class HelloResource {

    @CheckedTemplate
    public static class Templates {
        public static native TemplateInstance hello(String name); 1
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return Templates.hello(name); 2
    }
}
1 This declares a template with path templates/HelloResource/hello.
2 Templates.hello() returns a new template instance that is returned from the resource method. Note that we don’t trigger the rendering - this is done automatically by a special ContainerResponseFilter implementation.

声明了一个 @CheckedTemplate 类后,我们将检查其所有方法是否指向现有模板,因此,如果你尝试从你的 Java 代码中使用模板并且忘记添加它,我们将在构建时通知你 :)

Once you have declared a @CheckedTemplate class, we will check that all its methods point to existing templates, so if you try to use a template from your Java code and you forgot to add it, we will let you know at build time :)

请记住,此声明样式允许你引用其他资源中声明的模板:

Keep in mind this style of declaration allows you to reference templates declared in other resources too:

HelloResource.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;

@Path("goodbye")
public class GoodbyeResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return HelloResource.Templates.hello(name);
    }
}

Top-level type-safe templates

自然地,如果你想在顶级直接声明模板,例如 /src/main/resources/templates/hello.txt,则可以将它们声明在顶级(非嵌套)Templates 类中:

Naturally, if you want to declare templates at the top-level, directly in /src/main/resources/templates/hello.txt, for example, you can declare them in a top-level (non-nested) Templates class:

HelloResource.java
package org.acme.quarkus.sample;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;
import io.quarkus.qute.CheckedTemplate;

@CheckedTemplate
public class Templates {
    public static native TemplateInstance hello(String name); 1
}
1 This declares a template with path templates/hello.

Template Parameter Declarations

如果您在模板中声明 parameter declaration,那么 Qute 会尝试验证引用此参数的所有表达式,如果找到不正确的表达式,则构建失败。

If you declare a parameter declaration in a template then Qute attempts to validate all expressions that reference this parameter and if an incorrect expression is found the build fails.

我们假设我们有一个简单的类,如下所示:

Let’s suppose we have a simple class like this:

Item.java
public class Item {
    public String name;
    public BigDecimal price;
}

并且我们希望呈现一个包含商品名称和价格的简单 HTML 页面。

And we’d like to render a simple HTML page that contains the item name and price.

让我们从模板开始:

Let’s start again with the template:

ItemResource/item.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{item.name}</title> 1
</head>
<body>
    <h1>{item.name}</h1>
    <div>Price: {item.price}</div> 2
</body>
</html>
1 This expression is validated. Try to change the expression to {item.nonSense} and the build should fail.
2 This is also validated.

最后,让我们使用类型安全的模板创建一个资源类:

Finally, let’s create a resource class with type-safe templates:

ItemResource.java
package org.acme.quarkus.sample;

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;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;
import io.quarkus.qute.CheckedTemplate;

@Path("item")
public class ItemResource {

    @CheckedTemplate
    public static class Templates {
        public static native TemplateInstance item(Item item); 1
    }

    @GET
    @Path("{id}")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance get(@PathParam("id") Integer id) {
        return Templates.item(service.findItem(id)); 2
    }
}
1 Declare a method that gives us a TemplateInstance for templates/ItemResource/item.html and declare its Item item parameter so we can validate the template.
2 Make the Item object accessible in the template.

当启用 --parameters 编译器参数时,Quarkus REST 可以从方法参数名称推断参数名称,从而使 @PathParam("id") 注释在这种情况下可选。

When the --parameters compiler argument is enabled, Quarkus REST may infer the parameter names from the method argument names, making the @PathParam("id") annotation optional in this case.

Template parameter declaration inside the template itself

或者,你可以在模板文件本身中声明模板参数。

Alternatively, you can declare your template parameters in the template file itself.

让我们从模板开始:

Let’s start again with the template:

item.html
{@org.acme.Item item} 1
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{item.name}</title> 2
</head>
<body>
    <h1>{item.name}</h1>
    <div>Price: {item.price}</div>
</body>
</html>
1 Optional parameter declaration. Qute attempts to validate all expressions that reference the parameter item.
2 This expression is validated. Try to change the expression to {item.nonSense} and the build should fail.

最后,让我们创建一个资源类。

Finally, let’s create a resource class.

ItemResource.java
package org.acme.quarkus.sample;

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;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;

@Path("item")
public class ItemResource {

    @Inject
    ItemService service;

    @Inject
    Template item; 1

    @GET
    @Path("{id}")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance get(Integer id) {
        return item.data("item", service.findItem(id)); 2
    }
}
1 Inject the template with path templates/item.html.
2 Make the Item object accessible in the template.

Template Extension Methods

Template extension methods 用于扩展数据对象的可用属性集。

Template extension methods are used to extend the set of accessible properties of data objects.

有时,你无法控制要在模板中使用的类,并且你无法向其中添加方法。模板扩展方法允许你为那些类声明新方法,这些方法与它们属于目标类一样可以从你的模板中获得。

Sometimes, you’re not in control of the classes that you want to use in your template, and you cannot add methods to them. Template extension methods allows you to declare new method for those classes that will be available from your templates just as if they belonged to the target class.

让我们继续扩展我们的包含商品名称、价格的简单 HTML 页面,并添加一个折扣价格。折扣价有时称为“计算属性”。我们将实现一个模板扩展方法以便轻松地呈现此属性。让我们更新我们的模板:

Let’s keep extending on our simple HTML page that contains the item name, price and add a discounted price. The discounted price is sometimes called a "computed property". We will implement a template extension method to render this property easily. Let’s update our template:

HelloResource/item.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{item.name}</title>
</head>
<body>
    <h1>{item.name}</h1>
    <div>Price: {item.price}</div>
    {#if item.price > 100} 1
    <div>Discounted Price: {item.discountedPrice}</div> 2
    {/if}
</body>
</html>
1 if is a basic control flow section.
2 This expression is also validated against the Item class and obviously there is no such property declared. However, there is a template extension method declared on the TemplateExtensions class - see below.

最后,让我们创建一个类,将我们所有扩展方法都放在其中:

Finally, let’s create a class where we put all our extension methods:

TemplateExtensions.java
package org.acme.quarkus.sample;

import io.quarkus.qute.TemplateExtension;

@TemplateExtension
public class TemplateExtensions {

    public static BigDecimal discountedPrice(Item item) { 1
        return item.price.multiply(new BigDecimal("0.9"));
    }
}
1 A static template extension method can be used to add "computed properties" to a data class. The class of the first parameter is used to match the base object and the method name is used to match the property name.

如果你使用 `@TemplateExtension`注释,你可以在每个类中添加模板扩展方法,但我们建议按照目标类型进行分组或在单个 `TemplateExtensions`类中按照约定。

you can place template extension methods in every class if you annotate them with @TemplateExtension but we advise to keep them either grouped by target type, or in a single TemplateExtensions class by convention.

Rendering Periodic Reports

在呈现定期报告时,模板引擎也可以很有用。你需要首先添加 quarkus-schedulerquarkus-qute 扩展。在你的 pom.xml 文件中,添加:

Templating engine could be also very useful when rendering periodic reports. You’ll need to add the quarkus-scheduler and quarkus-qute extensions first. In your pom.xml file, add:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-qute</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-scheduler</artifactId>
</dependency>

假设我们有一个 SampleService bean,它的 get() 方法返回一个示例列表。

Let’s suppose we have a SampleService bean whose get() method returns a list of samples.

Sample.java
public class Sample {
    public boolean valid;
    public String name;
    public String data;
}

模板很简单:

The template is simple:

report.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Report {now}</title>
</head>
<body>
    <h1>Report {now}</h1>
    {#for sample in samples} 1
      <h2>{sample.name ?: 'Unknown'}</h2> 2
      <p>
      {#if sample.valid}
        {sample.data}
      {#else}
        <strong>Invalid sample found</strong>.
      {/if}
      </p>
    {/for}
</body>
</html>
1 The loop section makes it possible to iterate over iterables, maps and streams.
2 This value expression is using the elvis operator - if the name is null the default value is used.
ReportGenerator.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;

import io.quarkus.qute.Template;
import io.quarkus.qute.Location;
import io.quarkus.scheduler.Scheduled;

public class ReportGenerator {

    @Inject
    SampleService service;

    @Location("reports/v1/report_01") 1
    Template report;

    @Scheduled(cron="0 30 * * * ?") 2
    void generate() {
        String result = report
            .data("samples", service.get())
            .data("now", java.time.LocalDateTime.now())
            .render(); 3
        // Write the result somewhere...
    }
}
1 In this case, we use the @Location qualifier to specify the template path: templates/reports/v1/report_01.html.
2 Use the @Scheduled annotation to instruct Quarkus to execute this method on the half hour. For more information see the Scheduler guide.
3 The TemplateInstance.render() method triggers rendering. Note that this method blocks the current thread.

Qute Reference Guide

要了解更多关于 Qute 的信息,请参阅 Qute reference guide.

To learn more about Qute, please refer to the Qute reference guide.

Qute Configuration Reference

Unresolved directive in qute.adoc - include::{generated-dir}/config/quarkus-qute.adoc[]