编辑此页面

Qute 模板引擎

Qute 是专门为 Quarkus 开发的模板引擎。反射的使用被最小化,以减少原生镜像的大小。该 API 结合了命令式和非阻塞响应式编码风格。在开发模式下,位于 src/main/resources/templates 中的所有文件都会被监控更改,并且修改会立即生效。此外,我们的目标是在构建时检测到大多数模板问题。在本指南中,您将学习如何在应用程序中轻松渲染模板。

解决方案

我们建议您按照以下章节中的说明,逐步创建应用程序。但是,您可以直接转到完整的示例。

克隆 Git 仓库:git clone https://github.com/quarkusio/quarkus-quickstarts.git,或下载 存档

解决方案位于 qute-quickstart 目录中。

通过 HTTP 提供 Qute 模板

如果您想通过 HTTP 提供您的模板

  1. Qute Web 扩展允许您直接通过 HTTP 提供位于 src/main/resources/templates/pub/ 中的模板。在这种情况下,您不需要任何 Java 代码来“插入”模板,例如,模板 src/main/resources/templates/pub/foo.html 将默认从路径 /foo/foo.html 提供。

  2. 为了更精细的控制,您可以将其与 Quarkus REST 结合使用,以控制模板的提供方式。位于 src/main/resources/templates 目录及其子目录中的所有文件都将注册为模板,并且可以在 REST 资源中注入。

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 平台 BOM 中定义。

使用 Qute 提供 Hello World

让我们从一个 Hello World 模板开始

src/main/resources/templates/pub/hello.html
<h1>Hello {http:param('name', 'Quarkus')}!</h1> (1)
1 {http:param('name', 'Quarkus')} 是一个在渲染模板时计算的表达式(Quarkus 是默认值)。
位于 pub 目录中的模板通过 HTTP 提供。此行为是内置的,不需要任何控制器。例如,模板 src/main/resources/templates/pub/foo.html 将默认从路径 /foo/foo.html 提供。

一旦您的应用程序正在运行,您可以打开您的浏览器并导航到:https://:8080/hello?name=Martin

有关 Qute Web 选项的更多信息,请参阅 Qute Web 指南

Hello Qute 和 REST

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

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

一个非常简单的文本模板

hello.txt
Hello {name}! (1)
1 {name} 是一个在渲染模板时计算的值表达式。

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

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 如果没有提供 @Location 限定符,则字段名称用于定位模板。在本例中,我们正在注入一个路径为 templates/hello.txt 的模板。
2 Template.data() 返回一个新的模板实例,可以在触发实际渲染之前对其进行自定义。在本例中,我们将 name 值放在键 name 下。在渲染期间可以访问数据映射。
3 请注意,我们不会触发渲染 - 这是由特殊的 ContainerResponseFilter 实现自动完成的。

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

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

类型安全模板

有一种替代方法可以在 Java 代码中声明您的模板,它依赖于以下约定

  • 将您的模板文件组织在 /src/main/resources/templates 目录中,方法是将它们分组到每个资源类一个目录中。因此,如果您的 FruitResource 类引用了两个模板 applesoranges,请将它们放在 /src/main/resources/templates/FruitResource/apples.txt/src/main/resources/templates/FruitResource/oranges.txt 中。按资源类分组模板可以更轻松地导航到它们。

  • 在每个资源类中,在您的资源类中声明一个 @CheckedTemplate static class Template {} 类。

  • 为您的资源的每个模板文件声明一个 public static native TemplateInstance method();

  • 使用这些静态方法来构建您的模板实例。

这是前面的示例,使用这种样式重写

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

HelloResource/hello.txt
Hello {name}! (1)
1 {name} 是一个在渲染模板时计算的值表达式。

现在让我们在资源类中声明和使用此模板。

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 这声明了一个路径为 templates/HelloResource/hello 的模板。
2 Templates.hello() 返回一个新的模板实例,该实例从资源方法返回。请注意,我们不会触发渲染 - 这是由特殊的 ContainerResponseFilter 实现自动完成的。
一旦您声明了一个 @CheckedTemplate 类,我们将检查其所有方法是否都指向现有模板,因此如果您尝试从 Java 代码中使用模板并且忘记添加它,我们会在构建时通知您:)

请记住,这种声明样式允许您引用在其他资源中声明的模板

GreetingResource.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("greeting")
public class GreetingResource {

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

顶级类型安全模板

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

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 这声明了一个路径为 templates/hello 的模板。

模板参数声明

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

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

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

我们想渲染一个包含项目名称和价格的简单 HTML 页面。

让我们再次从模板开始

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 此表达式已验证。尝试将表达式更改为 {item.nonSense},构建应该失败。
2 这也已验证。

最后,让我们创建一个具有类型安全模板的资源类

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 声明一个方法,为我们提供 templates/ItemResource/item.htmlTemplateInstance,并声明其 Item item 参数,以便我们可以验证模板。
2 使 Item 对象在模板中可访问。
当启用 --parameters 编译器参数时,Quarkus REST 可能会从方法参数名称推断参数名称,在这种情况下,@PathParam("id") 注释是可选的。

模板本身内的模板参数声明

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

让我们再次从模板开始

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 可选参数声明。Qute 尝试验证所有引用参数 item 的表达式。
2 此表达式已验证。尝试将表达式更改为 {item.nonSense},构建应该失败。

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

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 注入路径为 templates/item.html 的模板。
2 使 Item 对象在模板中可访问。

模板扩展方法

模板扩展方法用于扩展数据对象的可访问属性集。

有时,您无法控制要在模板中使用的类,并且您无法向它们添加方法。模板扩展方法允许您为这些类声明新方法,这些方法将从您的模板中可用,就像它们属于目标类一样。

让我们继续扩展包含项目名称、价格的简单 HTML 页面,并添加一个折扣价。折扣价有时被称为“计算属性”。我们将实现一个模板扩展方法来轻松渲染此属性。让我们更新我们的模板

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 是一个基本控制流部分。
2 此表达式也针对 Item 类进行验证,显然没有声明这样的属性。但是,在 TemplateExtensions 类上声明了一个模板扩展方法 - 请参见下文。

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

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 静态模板扩展方法可用于向数据类添加“计算属性”。第一个参数的类用于匹配基本对象,方法名称用于匹配属性名称。
如果您使用 @TemplateExtension 注释它们,则可以将模板扩展方法放在每个类中,但我们建议按照约定将它们按目标类型分组,或放在单个 TemplateExtensions 类中。

渲染定期报告

模板引擎对于渲染定期报告也非常有用。您需要首先添加 quarkus-schedulerquarkus-qute 扩展。在您的 pom.xml 文件中,添加

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

假设我们有一个 SampleService bean,其 get() 方法返回一个样本列表。

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

模板很简单

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 循环部分使得可以迭代可迭代对象、映射和流。
2 此值表达式正在使用 elvis 运算符 - 如果名称为 null,则使用默认值。
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 在本例中,我们使用 @Location 限定符来指定模板路径:templates/reports/v1/report_01.html
2 使用 @Scheduled 注释来指示 Quarkus 每半小时执行此方法。有关更多信息,请参阅 Scheduler 指南。
3 TemplateInstance.render() 方法触发渲染。请注意,此方法会阻塞当前线程。

Qute 参考指南

要了解有关 Qute 的更多信息,请参阅 Qute 参考指南

Qute 配置参考

构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖

配置属性

类型

默认

尝试查找模板文件时使用的后缀列表。

默认情况下,engine.getTemplate("foo") 会导致多次查找:foofoo.htmlfoo.txt 等。

环境变量:QUARKUS_QUTE_SUFFIXES

显示更多

字符串列表

qute.html,qute.txt,html,txt

后缀到内容类型的附加映射。当使用模板变体时,将使用此映射。默认情况下,java.net.URLConnection#getFileNameMap() 用于确定模板文件的内容类型。

环境变量:QUARKUS_QUTE_CONTENT_TYPES__FILE_SUFFIX_

显示更多

Map<String,String>

用于在执行类型安全验证时有意忽略表达式某些部分的排除规则列表。

元素值必须至少有两部分,用点分隔。最后一部分用于匹配属性/方法名称。前置部分用于匹配类名。值 * 可用于匹配任何名称。

示例

  • org.acme.Foo.name - 排除 org.acme.Foo 类上的属性/方法 name

  • org.acme.Foo.* - 排除 org.acme.Foo 类上的任何属性/方法

  • *.age - 排除任何类上的属性/方法 age

环境变量:QUARKUS_QUTE_TYPE_CHECK_EXCLUDES

显示更多

字符串列表

此正则表达式用于排除在模板根目录中找到的模板文件。排除的模板在构建期间既不解析也不验证,并且在运行时不可用。

匹配的输入是相对于根目录的文件路径,/ 用作路径分隔符。

默认情况下,隐藏文件被排除。隐藏文件的名称以点开头。

环境变量:QUARKUS_QUTE_TEMPLATE_PATH_EXCLUDE

显示更多

Pattern (模式)

^\..|.\/\..*$

前缀用于访问循环部分内的迭代元数据。

有效的前缀由字母数字字符和下划线组成。可以使用三个特殊常量

  • <alias_> - 使用迭代元素的别名后缀下划线,例如 item_hasNextit_count

  • <alias?> - 使用迭代元素的别名后缀问号,例如 item?hasNextit?count

  • <none> - 不使用前缀,例如 hasNextcount

默认情况下,设置 <alias_> 常量。

环境变量:QUARKUS_QUTE_ITERATION_METADATA_PREFIX

显示更多

字符串

<alias_>

如果设置了模板变体,则 '"<>& 字符将被转义的内容类型列表。

环境变量:QUARKUS_QUTE_ESCAPE_CONTENT_TYPES

显示更多

字符串列表

text/html,text/xml,application/xml,application/xhtml+xml

模板文件的默认字符集。

环境变量:QUARKUS_QUTE_DEFAULT_CHARSET

显示更多

Charset (字符集)

UTF-8

在应用程序中找到具有相同路径的多个模板时使用的策略。

环境变量:QUARKUS_QUTE_DUPLICIT_TEMPLATES_STRATEGY

显示更多

prioritize如果找到具有相同路径的多个模板,则确定最高优先级值,并消除所有具有最低优先级的模板。如果只剩下一个模板,则使用此模板。否则,构建失败。来自根应用程序存档的模板的优先级为 30。来自其他应用程序存档的模板的优先级为 10。来自构建项的模板可以定义任何优先级。, fail如果找到具有相同路径的多个模板,则构建失败。

prioritize如果找到具有相同路径的多个模板,则确定最高优先级值,并消除所有具有最低优先级的模板。如果只剩下一个模板,则使用此模板。否则,构建失败。来自根应用程序存档的模板的优先级为 {@code 30}。来自其他应用程序存档的模板的优先级为 {@code 10}。来自构建项的模板可以定义任何优先级。

默认情况下,模板修改会导致应用程序重新启动,从而触发构建时验证。

此正则表达式可用于指定应用程序不重新启动的模板。即,模板被重新加载,并且仅执行运行时验证。

匹配的输入是以模板根目录开头的模板路径,/ 用作路径分隔符。例如,templates/foo.html

环境变量:QUARKUS_QUTE_DEV_MODE_NO_RESTART_TEMPLATES

显示更多

Pattern (模式)

默认情况下,注入的和类型安全的模板的渲染结果记录在托管的 RenderedResults 中,该 RenderedResults 注册为 CDI bean。

环境变量:QUARKUS_QUTE_TEST_MODE_RECORD_RENDERED_RESULTS

显示更多

布尔值

true

当独立表达式在运行时计算为“未找到”值,并且 quarkus.qute.strict-rendering 配置属性设置为 false 时使用的策略

评估节参数时,从不使用此策略,例如 {#if foo.name}。在这种情况下,节有责任适当地处理这种情况。

默认情况下,NOT_FOUND 常量写入输出。但是,在开发模式下,默认情况下使用 PropertyNotFoundStrategy#THROW_EXCEPTION,即,当未指定策略时。

环境变量:QUARKUS_QUTE_PROPERTY_NOT_FOUND_STRATEGY

显示更多

default输出 NOT_FOUND 常量。, noop无操作 - 无输出。, throw-exception抛出 TemplateException, output-original输出原始表达式字符串,例如 {foo.name}

指定解析器是否应从输出中删除独立行。独立行是包含至少一个节标记、参数声明或注释但不包含表达式且不包含非空格字符的行。

环境变量:QUARKUS_QUTE_REMOVE_STANDALONE_LINES

显示更多

布尔值

true

如果设置为 true,则任何计算为 Results.NotFound 值的表达式都将始终导致 TemplateException,并且渲染将被中止。

请注意,如果启用了严格渲染,则完全忽略 quarkus.qute.property-not-found-strategy 配置属性。

环境变量:QUARKUS_QUTE_STRICT_RENDERING

显示更多

布尔值

true

全局渲染超时(以毫秒为单位)。如果未设置 timeout 模板实例属性,则使用它。

环境变量:QUARKUS_QUTE_TIMEOUT

显示更多

long

10000

如果设置为 true,则超时也应可用于异步渲染方法,例如 TemplateInstance#createUni()TemplateInstance#renderAsync()

环境变量:QUARKUS_QUTE_USE_ASYNC_TIMEOUT

显示更多

布尔值

true

相关内容