Qute 模板引擎
Qute 是专门为 Quarkus 开发的模板引擎。反射的使用被最小化,以减少原生镜像的大小。该 API 结合了命令式和非阻塞响应式编码风格。在开发模式下,位于 src/main/resources/templates
中的所有文件都会被监控更改,并且修改会立即生效。此外,我们的目标是在构建时检测到大多数模板问题。在本指南中,您将学习如何在应用程序中轻松渲染模板。
解决方案
我们建议您按照以下章节中的说明,逐步创建应用程序。但是,您可以直接转到完整的示例。
克隆 Git 仓库:git clone https://github.com/quarkusio/quarkus-quickstarts.git
,或下载 存档。
解决方案位于 qute-quickstart
目录中。
通过 HTTP 提供 Qute 模板
如果您想通过 HTTP 提供您的模板
-
Qute Web 扩展允许您直接通过 HTTP 提供位于
src/main/resources/templates/pub/
中的模板。在这种情况下,您不需要任何 Java 代码来“插入”模板,例如,模板src/main/resources/templates/pub/foo.html
将默认从路径/foo
和/foo.html
提供。 -
为了更精细的控制,您可以将其与 Quarkus REST 结合使用,以控制模板的提供方式。位于
src/main/resources/templates
目录及其子目录中的所有文件都将注册为模板,并且可以在 REST 资源中注入。
<dependency>
<groupId>io.quarkiverse.qute.web</groupId>
<artifactId>quarkus-qute-web</artifactId>
</dependency>
implementation("io.quarkiverse.qute.web:quarkus-qute-web")
Qute Web 扩展虽然托管在 Quarkiverse 中,但它是 Quarkus 平台的一部分,其版本在 Quarkus 平台 BOM 中定义。 |
使用 Qute 提供 Hello World
让我们从一个 Hello World 模板开始
<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 结合使用,以控制模板的提供方式
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest")
一个非常简单的文本模板
Hello {name}! (1)
1 | {name} 是一个在渲染模板时计算的值表达式。 |
现在让我们将“已编译”的模板注入到资源类中。
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
类引用了两个模板apples
和oranges
,请将它们放在/src/main/resources/templates/FruitResource/apples.txt
和/src/main/resources/templates/FruitResource/oranges.txt
中。按资源类分组模板可以更轻松地导航到它们。 -
在每个资源类中,在您的资源类中声明一个
@CheckedTemplate static class Template {}
类。 -
为您的资源的每个模板文件声明一个
public static native TemplateInstance method();
。 -
使用这些静态方法来构建您的模板实例。
这是前面的示例,使用这种样式重写
我们将从一个非常简单的模板开始
Hello {name}! (1)
1 | {name} 是一个在渲染模板时计算的值表达式。 |
现在让我们在资源类中声明和使用此模板。
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 代码中使用模板并且忘记添加它,我们会在构建时通知您:) |
请记住,这种声明样式允许您引用在其他资源中声明的模板
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
类中声明它们
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 会尝试验证所有引用此参数的表达式,如果找到不正确的表达式,则构建失败。
假设我们有一个简单的类,如下所示
public class Item {
public String name;
public BigDecimal price;
}
我们想渲染一个包含项目名称和价格的简单 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 | 这也已验证。 |
最后,让我们创建一个具有类型安全模板的资源类
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.html 的 TemplateInstance ,并声明其 Item item 参数,以便我们可以验证模板。 |
2 | 使 Item 对象在模板中可访问。 |
当启用 --parameters 编译器参数时,Quarkus REST 可能会从方法参数名称推断参数名称,在这种情况下,@PathParam("id") 注释是可选的。 |
模板本身内的模板参数声明
或者,您可以在模板文件本身中声明模板参数。
让我们再次从模板开始
{@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} ,构建应该失败。 |
最后,让我们创建一个资源类。
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 页面,并添加一个折扣价。折扣价有时被称为“计算属性”。我们将实现一个模板扩展方法来轻松渲染此属性。让我们更新我们的模板
<!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 类上声明了一个模板扩展方法 - 请参见下文。 |
最后,让我们创建一个类,将所有扩展方法放在其中
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-scheduler
和 quarkus-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()
方法返回一个样本列表。
public class Sample {
public boolean valid;
public String name;
public String data;
}
模板很简单
<!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,则使用默认值。 |
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 配置参考
构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖
配置属性 |
类型 |
默认 |
---|---|---|
尝试查找模板文件时使用的后缀列表。 默认情况下, 环境变量: 显示更多 |
字符串列表 |
|
后缀到内容类型的附加映射。当使用模板变体时,将使用此映射。默认情况下, 环境变量: 显示更多 |
Map<String,String> |
|
用于在执行类型安全验证时有意忽略表达式某些部分的排除规则列表。 元素值必须至少有两部分,用点分隔。最后一部分用于匹配属性/方法名称。前置部分用于匹配类名。值 示例
环境变量: 显示更多 |
字符串列表 |
|
此正则表达式用于排除在模板根目录中找到的模板文件。排除的模板在构建期间既不解析也不验证,并且在运行时不可用。 匹配的输入是相对于根目录的文件路径, 默认情况下,隐藏文件被排除。隐藏文件的名称以点开头。 环境变量: 显示更多 |
|
|
前缀用于访问循环部分内的迭代元数据。 有效的前缀由字母数字字符和下划线组成。可以使用三个特殊常量
默认情况下,设置 环境变量: 显示更多 |
字符串 |
|
如果设置了模板变体,则 环境变量: 显示更多 |
字符串列表 |
|
|
||
在应用程序中找到具有相同路径的多个模板时使用的策略。 环境变量: 显示更多 |
|
|
默认情况下,模板修改会导致应用程序重新启动,从而触发构建时验证。 此正则表达式可用于指定应用程序不重新启动的模板。即,模板被重新加载,并且仅执行运行时验证。 匹配的输入是以模板根目录开头的模板路径, 环境变量: 显示更多 |
||
默认情况下,注入的和类型安全的模板的渲染结果记录在托管的 环境变量: 显示更多 |
布尔值 |
|
当独立表达式在运行时计算为“未找到”值,并且 评估节参数时,从不使用此策略,例如 默认情况下, 环境变量: 显示更多 |
|
|
指定解析器是否应从输出中删除独立行。独立行是包含至少一个节标记、参数声明或注释但不包含表达式且不包含非空格字符的行。 环境变量: 显示更多 |
布尔值 |
|
如果设置为 请注意,如果启用了严格渲染,则完全忽略 环境变量: 显示更多 |
布尔值 |
|
long |
|
|
如果设置为 环境变量: 显示更多 |
布尔值 |
|