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
.
Serving Qute templates via HTTP
If you want to serve your templates via HTTP:
-
The Qute Web extension allows you to directly serve via HTTP templates located in
src/main/resources/templates/pub/
. In that case you don’t need any Java code to "plug" the template, for example, the templatesrc/main/resources/templates/pub/foo.html
will be served from the paths/foo
and/foo.html
by default. -
For finer control, you can combine it with Quarkus REST to control how your template will be served. All files located in the
src/main/resources/templates
directory and its subdirectories are registered as templates and can be injected in a REST resource.
<dependency>
<groupId>io.quarkiverse.qute.web</groupId>
<artifactId>quarkus-qute-web</artifactId>
</dependency>
implementation("io.quarkiverse.qute.web:quarkus-qute-web")
The Qute Web extension, while hosted in the Quarkiverse, is part of the Quarkus Platform and its version is defined in the Quarkus Platform BOM. |
Serving Hello World with Qute
Let’s start with a Hello World template:
<h1>Hello {http:param('name', 'Quarkus')}!</h1> (1)
1 | {http:param('name', 'Quarkus')} is an expression that is evaluated when the template is rendered (Quarkus is the default value). |
Templates located in the pub directory are served via HTTP. This behavior is built-in, no controllers are needed. For example, the template src/main/resources/templates/pub/foo.html will be served from the paths /foo and /foo.html by default.
|
If your application is running, you can open your browser and hit: http://localhost:8080/hello?name=Martin
For more information about Qute Web options, see the Qute Web guide.
Hello Qute and REST
For finer control, you can combine Qute Web with Quarkus REST or Quarkus RESTEasy to control how your template will be served
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest")
A very simple text template:
Hello {name}! (1)
1 | {name} é uma expressão de valor que é avaliada quando o modelo é processado. |
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.
Configuration property |
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 additional map of suffixes to content types. This map is used when working with template variants. By default, the Environment variable: Show more |
Map<String,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 |
|