Qute - 另一个模板引擎(为何如此)
让我们从一个很好的问题开始:“为什么又一个模板引擎?”。Java 中有很多模板库。Quarkus 以构建在“一流库和标准”之上而闻名。这是真的。另一方面,Quarkus 社区也是一个强大的创新催化剂。因此,我们决定启动 Qute(QUarkus TEmplates)——一个专门为满足 Quarkus 需求而设计的模板引擎。我们相信,即使在模板这样一个已经被广泛探索的领域,我们也能带来新的想法。
异步数据解析 - 通往响应式之路
在我们开始设计 Qute 时,我们有一个重要的方面——数据解析 API 应该是异步的。这允许更好的资源利用,并符合 Quarkus 的响应式模型。此设计决策的另一个后果是,可以直接从模板利用非阻塞客户端,即从各种源异步获取数据。
{@org.acme.Client client} (1)
<html>
<body>
<h1>Quarkus Open Pull Requests</h1>
{#for pull in client.pullRequests} (2)
<p>{pull.title} - {pull.user.login}</p>
{/for}
</body>
</html>
1 | 参数声明 - 将 client 映射到 org.acme.Client 。有关更多信息,请参阅 下一节。 |
2 | org.acme.Client#getPullRequests() 使用非阻塞 Vert.x 客户端直接从 GitHub API 获取数据。由于数据解析是异步的,线程不会被阻塞,可以继续执行其他任务。
|
类型安全模板
大多数模板引擎都不是类型安全的,即不防止类型错误。这很自然,因为模板中的动态性通常非常实用。另一方面,用户不会免受由拼写错误和各种重构后果引起的繁琐错误的影响。Qute 模板可以选择性地进行类型安全。这到底意味着什么?一个模板可以包含一个或多个参数声明。参数声明将具体的类型信息绑定到当前上下文中的给定标识符。拥有类型安全模板有什么好处?
-
Quarkus 会验证引用参数声明的所有表达式。如果找到无效/不正确的表达式,构建将失败。
在开发模式下,src/main/resources/templates 目录中的所有文件都会被监视更改,修改会立即可见。这也意味着,只要出现类型错误,您的应用程序就会快速失败。 |
-
为参数声明中使用的所有类型生成值解析器,以便无需反射即可访问其属性。这在定位 GraalVM 原生镜像时非常有用。
-
我们的待办事项清单还有一些想法,例如类型安全表达式的性能优化等。
{@org.acme.Foo foo} (1)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Qute Hello</title>
</head>
<body>
<h1>{title}</h1> (2)
<p>{foo.message}</p> (3)
{#for foo in baz.foos}
<p>Hello {foo.message}!</p> (4)
{/for}
</body>
</html>
1 | 参数声明 - 将 foo 映射到 org.acme.Foo 。 |
2 | {title} 未经验证 - 与参数声明不匹配。 |
3 | {foo.message} 已经验证。org.acme.Foo 必须具有 message 属性或存在匹配的模板扩展方法。 |
4 | {foo.message} 未经验证,因为 foo 在循环部分被覆盖,并且没有可用的类型信息。 |
目前仅在表达式中验证属性;“虚拟方法”(如 foo.getBar(baz.name) )当前被忽略。 |
一流的 Quarkus 公民
尽管 Qute 对 Quarkus 进行了高度优化,但核心引擎被开发为一个独立的库,可以集成到任何环境中。 |
在 Quarkus 中,src/main/resources/templates
目录中的所有模板都经过验证,并且可以轻松注入。
package org.acme.qute;
import io.quarkus.qute.Template;
class MyBean {
@Inject
Template items; (1)
@Inject
Service service;
String renderItems() {
return items.data("items", service.getItems()).render(); (2)
}
}
1 | 字段名用于定位模板。在此特定情况下,容器将尝试定位路径为 src/main/resources/templates/items.html 的模板。如果不存在这样的模板,构建将失败。 |
2 | 请参阅 Hello World 示例 来探索基本工作流程。 |
此外,还提供了一个预配置的 Engine
实例,可供注入。Engine
是模板管理的核心点,并提供了一些底层 API。
RESTEasy 集成
如果与 RESTEasy 一起使用,资源方法可以返回 TemplateInstance
,集成代码将负责所有必要的步骤并将输出呈现给响应。有关更多信息,请参阅 RESTEasy 集成。
package org.acme.qute;
...
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) {
// the template looks like: Hello {name}!
return hello.data("name", name); (2) (3)
}
}
1 | 字段名用于定位模板。在此特定情况下,我们注入的是路径为 templates/hello.txt 的模板。 |
2 | Template.data() 返回一个新的模板实例,可以在触发实际渲染之前对其进行自定义。在本例中,我们将 name 值放在键 name 下。在渲染期间可以访问数据映射。 |
3 | 请注意,我们不会触发渲染 - 这是由特殊的 ContainerResponseFilter 实现自动完成的。 |
Mailer 集成
创建电子邮件消息时,模板可能很有用。Mailer 扩展与 Qute 集成,提供了发送电子邮件的便捷方式。特别是,消息正文是使用 src/main/resources/templates
目录中的 *.html
和 *.txt
模板自动创建的。有关更多详细信息,请参阅 发送电子邮件 指南。