Motor de template Qute
Qute é um mecanismo de modelagem projetado especificamente para atender às necessidades do Quarkus. O uso de reflexão é minimizado para reduzir o tamanho das imagens nativas. A API combina tanto o estilo imperativo quanto o estilo de codificação reativo e não bloqueante. No modo de desenvolvimento, todos os arquivos localizados em src/main/resources/templates
são monitorados para alterações, e as modificações são imediatamente visíveis. Além disso, tentamos detectar a maioria dos problemas de modelo durante o tempo de compilação. Neste guia, você aprenderá como renderizar facilmente modelos em sua aplicação.
Solução
Recomendamos que siga as instruções nas seções seguintes e crie a aplicação passo a passo. No entanto, você pode ir diretamente para o exemplo completo.
Clone o repositório Git: git clone https://github.com/quarkusio/quarkus-quickstarts.git
, ou baixe um arquivo.
A solução está localizada no diretório qute-quickstart
.
Hello World com Jakarta REST
Se quiser utilizar o Qute na sua aplicação Jakarta REST, tem de adicionar primeiro uma extensão:
-
either
quarkus-rest-qute
if you are using Quarkus REST (formerly RESTEasy Reactive):pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-rest-qute</artifactId> </dependency>
build.gradleimplementation("io.quarkus:quarkus-rest-qute")
-
ou
quarkus-resteasy-qute
se estiver utilizando o RESTEasy Clássico:pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-qute</artifactId> </dependency>
build.gradleimplementation("io.quarkus:quarkus-resteasy-qute")
Vamos começar com um modelo muito simples:
Hello {name}! (1)
1 | {name} é uma expressão de valor que é avaliada quando o modelo é processado. |
Por padrão, todos os arquivos localizados no diretório src/main/resources/templates e seus subdiretórios são registrados como modelos. Os modelos são validados durante o início da aplicação e monitorados para alterações no modo de desenvolvimento.
|
Agora vamos injetar o modelo "compilado" na classe de recurso.
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 | Se não for fornecido um qualificador @Location , o nome do campo é utilizado para localizar o modelo/template. Neste caso específico, vamos injetar um modelo com o caminho templates/hello.txt . |
2 | O Template.data() retorna uma nova instância de modelo que pode ser configurado antes que a renderização. Neste caso, colocamos o valor do nome na chave name . O mapa de dados está acessível durante a renderização. |
3 | Note-se que não acionamos a renderização - isto é feito automaticamente por uma implementação especial do ContainerResponseFilter . |
Se sua aplicação estiver rodando, você pode fazer uma requisição ao endpoint:
$ curl -w "\n" http://localhost:8080/hello?name=Martin
Hello Martin!
Type-safe templates(Templates com tipos seguros)
Existe uma forma alternativa de declarar os seus modelos no seu código Java, que se baseia na seguinte convenção:
-
Organize os seus arquivos de modelos no diretório
/src/main/resources/templates
, agrupando-os em um diretório por classe de recurso. Assim, se a sua classeItemResource
faz referência a dois modeloshello
egoodbye
, coloque-os em/src/main/resources/templates/ItemResource/hello.txt
e/src/main/resources/templates/ItemResource/goodbye.txt
. O agrupamento de modelos por classe de recurso facilita a navegação até eles. -
Em cada uma das suas classes de recursos, declare uma classe
@CheckedTemplate static class Template {}
dentro da sua classe de recursos. -
Declare um
public static native TemplateInstance method();
por arquivo modelo para o seu recurso. -
Utilize esses métodos estáticos para criar as suas instâncias de modelo.
Aqui está o exemplo anterior, reescrito com este estilo:
Vamos começar com um modelo muito simples:
Hello {name}! (1)
1 | {name} é uma expressão de valor que é avaliada quando o modelo é processado. |
Agora vamos declarar e utilizar esses modelos na classe de recurso.
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 | Isto declara um modelo com o caminho templates/HelloResource/hello . |
2 | Templates.hello() retorna uma nova instância de modelo que é gerada pelo método de recurso. Observe que não acionamos a renderização - isso é feito automaticamente por uma implementação especial de ContainerResponseFilter . |
Depois de declarar uma classe @CheckedTemplate , verificaremos se todos os seus métodos apontam para modelos existentes, por isso, informaremos no tempo de compilação, caso tenha esquecido de adicionar um modelo no seu projeto :)
|
Não se esqueça de que este estilo de declaração também permite fazer referência a modelos declarados em outros recursos:
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
Naturalmente, se quiser declarar os modelos ao nível superior, diretamente em /src/main/resources/templates/hello.txt
, por exemplo, pode declará-los numa classe Templates
de nível superior (não aninhada):
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 | Isto declara um modelo com o caminho templates/hello . |
Declarações de parâmetros de modelo
Se você declarar uma declaração de parâmetro em um modelo, o Qute tentará validar todas as expressões que fazem referência a esse parâmetro e, se uma expressão incorreta for encontrada, a compilação falhará.
Vamos supor que temos uma classe simples como esta:
public class Item {
public String name;
public BigDecimal price;
}
E gostaríamos de renderizar uma página HTML simples que contenha o nome e o preço do item.
Vamos começar de novo com o modelo:
<!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 | Esta expressão é validada. Se tentar alterar a expressão para {item.nonSense} , a construção deverá falhar. |
2 | Isto também é validado. |
Finalmente, vamos criar uma classe de recurso com modelos type-safe:
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 um método que nos dê um TemplateInstance para templates/ItemResource/item.html e declare o seu parâmetro Item item para podermos validar o modelo. |
2 | Tornar o objeto Item acessível no modelo. |
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.
|
Declaração do parâmetro do modelo dentro do próprio modelo
Em alternativa, pode declarar os seus parâmetros de modelo no próprio arquivo de modelo.
Vamos começar de novo com o modelo:
{@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 | Declaração de parâmetro opcional. O Qute tenta validar todas as expressões que fazem referência ao parâmetro item . |
2 | Esta expressão é validada. Se tentar alterar a expressão para {item.nonSense} , a construção deverá falhar. |
Finalmente, vamos criar uma classe de recurso.
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 | Injetar o modelo com o caminho templates/item.html . |
2 | Tornar o objeto Item acessível no modelo. |
Métodos de extensão de modelos
Os métodos de extensão de modelos são utilizados para estender o conjunto de propriedades acessíveis dos objetos de dados.
Por vezes, você não tem controle sobre as classes que deseja usar em seu modelo e não pode adicionar métodos às mesmas. Os métodos de extensão de modelos permitem-lhe declarar novos métodos para essas classes que estarão disponíveis a partir dos seus modelos, tal como se pertencessem à classe de destino.
Vamos continuar a estender a nossa página HTML simples que contém o nome do item, o preço e adicionar um preço com desconto. O preço com desconto é por vezes designado por “propriedade computada”. Vamos implementar um método de extensão de modelo para processar esta propriedade facilmente. Vamos atualizar o nosso modelo:
<!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 é uma instrução básica do fluxo de controle. |
2 | Esta expressão é também validada em relação à classe Item e, obviamente, não existe tal propriedade declarada. No entanto, existe um método de extensão de modelo declarado na classe TemplateExtensions - veja abaixo. |
Finalmente, vamos criar uma classe onde colocamos todos os nossos métodos de extensão:
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 | Um método de extensão de modelo estático pode ser utilizado para adicionar “propriedades computadas” a uma classe de dados. A classe do primeiro parâmetro é utilizada para corresponder ao objeto base e o nome do método é utilizado para corresponder ao nome da propriedade. |
pode colocar métodos de extensão de modelos em todas as classes se os anotar com @TemplateExtension , mas aconselhamos a mantê-los agrupados por tipo de destino ou numa única classe TemplateExtensions por convenção.
|
Renderização de relatórios periódicos
O mecanismo de criação de modelos também pode ser muito útil na apresentação de relatórios periódicos. Primeiro, é necessário adicionar as extensões quarkus-scheduler
e quarkus-qute
. No seu arquivo pom.xml
, adicione:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-qute</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-scheduler</artifactId>
</dependency>
Vamos supor que temos um bean SampleService
cujo método get()
devolve uma lista de amostras.
public class Sample {
public boolean valid;
public String name;
public String data;
}
O modelo é simples:
<!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 | A instrução de repetição permite iterar sobre iteráveis, mapas e fluxos. |
2 | Esta expressão de valor está utilizando o operador el vis - se o nome for nulo, é utilizado o valor predefinido. |
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 | Neste caso, utilizamos o qualificador @Location para especificar o caminho do modelo: templates/reports/v1/report_01.html . |
2 | Utilize a anotação @Scheduled para instruir o Quarkus a executar este método a cada meia hora. Para mais informações, consulte o guia Scheduler. |
3 | O método TemplateInstance.render() aciona a renderização. Note-se que este método bloqueia a thread atual. |
Guia de Referência do Qute
Para saber mais sobre a Qute, consulte o guia de referência da Qute.
Referência de configuração do Qute
Propriedade de Configuração Fixa no Momento da Compilação - Todas as outras propriedades de configuração podem ser sobrepostas em tempo de execução.
Tipo |
Padrão |
|
---|---|---|
The list of suffixes used when attempting to locate a template file. By default, Environment variable: Show more |
list of string |
|
The list of exclude rules used to intentionally ignore some parts of an expression when performing type-safe validation. An element value must have at least two parts separated by dot. The last part is used to match the property/method name. The prepended parts are used to match the class name. The value Examples:
Environment variable: Show more |
list of string |
|
This regular expression is used to exclude template files from the The matched input is the file path relative from the By default, the hidden files are excluded. The name of a hidden file starts with a dot. Environment variable: Show more |
|
|
The prefix is used to access the iteration metadata inside a loop section. A valid prefix consists of alphanumeric characters and underscores. Three special constants can be used:
Environment variable: Show more |
string |
|
The list of content types for which the Environment variable: Show more |
list of string |
|
The default charset of the templates files. Environment variable: Show more |
|
|
By default, a template modification results in an application restart that triggers build-time validations. This regular expression can be used to specify the templates for which the application is not restarted. I.e. the templates are reloaded and only runtime validations are performed. The matched input is the template path that starts with a template root, and the Environment variable: Show more |
||
By default, the rendering results of injected and type-safe templates are recorded in the managed Environment variable: Show more |
boolean |
|
The strategy used when a standalone expression evaluates to a "not found" value at runtime and the This strategy is never used when evaluating section parameters, e.g. By default, the Environment variable: Show more |
|
|
Specify whether the parser should remove standalone lines from the output. A standalone line is a line that contains at least one section tag, parameter declaration, or comment but no expression and no non-whitespace character. Environment variable: Show more |
boolean |
|
If set to Note that the Environment variable: Show more |
boolean |
|
The global rendering timeout in milliseconds. It is used if no Environment variable: Show more |
long |
|
If set to Environment variable: Show more |
boolean |
|
The additional map of suffixes to content types. This map is used when working with template variants. By default, the Environment variable: Show more |
|