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.
The solution is located in the qute-quickstart directory.
Hello World with JAX-RS
If you want to use Qute in your JAX-RS application, you need to add an extension first:
-
ou
quarkus-resteasy-reactive-qutese estiver utilizando o RESTEasy Reactive:pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-reactive-qute</artifactId> </dependency>build.gradleimplementation("io.quarkus:quarkus-resteasy-reactive-qute") -
ou
quarkus-resteasy-qutese 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 javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
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 classeItemResourcefaz referência a dois modeloshelloegoodbye, coloque-os em/src/main/resources/templates/ItemResource/hello.txte/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 javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
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 javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
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);
}
}
Templates e Níveis superiores.
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 javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.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(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. |
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 javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.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 javax.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 Environment variable: Show more |
list of string |
|
This regular expression is used to exclude template files from the 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 |
|
|
The strategy used when a standalone expression evaluates to a "not found" value at runtime and 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 |
booleano |
|
If set to Environment variable: Show more |
booleano |
|
The global rendering timeout in milliseconds. It is used if no Environment variable: Show more |
long |
|
If set to Environment variable: Show more |
booleano |
|
The additional map of suffixes to content types. This map is used when working with template variants. By default, the Environment variable: Show more |
|