Motor de plantillas Qute
Qute es un motor de plantillas desarrollado específicamente para Quarkus.
Se minimiza el uso de la reflexión para reducir el tamaño de las imágenes nativas.
La API combina tanto el estilo de codificación imperativo como el reactivo no bloqueante.
En el modo de desarrollo, todos los archivos ubicados en src/main/resources/templates se supervisan en busca de cambios, y las modificaciones se hacen visibles inmediatamente.
Además, nuestro objetivo es detectar la mayoría de los problemas de plantilla en el momento de la compilación.
En esta guía, aprenderá a renderizar fácilmente plantillas en su aplicación.
Solución
Recomendamos que siga las instrucciones de las siguientes secciones y cree la aplicación paso a paso. Sin embargo, también puede ir directamente al ejemplo completo.
Clone el repositorio Git: git clone https://github.com/quarkusio/quarkus-quickstarts.git o descargue un archivo.
La solución se encuentra en el qute-quickstart directorio.
Servir plantillas Qute a través de HTTP
Si desea servir sus plantillas a través de HTTP:
-
La extensión Web de Qute le permite servir directamente a través de HTTP plantillas ubicadas en
src/main/resources/templates/pub/. En ese caso no necesita ningún código Java para "conectar" la plantilla, por ejemplo, la plantillasrc/main/resources/templates/pub/foo.htmlse servirá desde las rutas/fooy/foo.htmlpor defecto. -
Para un control más fino, puede combinarlo con Quarkus REST para controlar cómo se servirá su plantilla. Todos los archivos ubicados en el directorio
src/main/resources/templatesy sus subdirectorios se registran como plantillas y pueden inyectarse en un recurso REST.
<dependency>
<groupId>io.quarkiverse.qute.web</groupId>
<artifactId>quarkus-qute-web</artifactId>
</dependency>
implementation("io.quarkiverse.qute.web:quarkus-qute-web")
| La extensión Qute Web, aunque está alojada en Quarkiverse, forma parte de Quarkus Platform y su versión está definida en la lista de materiales de Quarkus Platform. |
Sirviendo Hola Mundo con Qute
Empecemos con una plantilla Hola Mundo:
<h1>Hello {http:param('name', 'Quarkus')}!</h1> (1)
| 1 | {http:param('name', 'Quarkus')} es una expresión que se evalúa cuando se renderiza la plantilla (Quarkus es el valor por defecto). |
Las plantillas ubicadas en el directorio pub se sirven a través de HTTP. Este comportamiento está incorporado, no se necesitan controladores. Por ejemplo, la plantilla src/main/resources/templates/pub/foo.html se servirá desde las rutas /foo y /foo.html por defecto.
|
Una vez que su aplicación se esté ejecutando, puede abrir su navegador y navegar a: http://localhost:8080/hello?name=Martin
Para más información sobre las opciones de Qute Web, consulte la guía de Qute Web.
Hola Qute y REST
Para un control más preciso, puede combinar Qute Web con Quarkus REST (anteriormente RESTEasy Reactive) o la extensión heredada basada en RESTEasy Classic para controlar cómo se servirá su plantilla.
Utilizando la extensión quarkus-rest-qute:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-qute</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest-qute")
Una plantilla de texto muy sencilla:
Hello {name}! (1)
| 1 | {name} es una expresión de valor que se evalúa cuando se renderiza la plantilla. |
Ahora vamos a inyectar la plantilla "compilada" en la clase de 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;
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 | Si no se proporciona el calificador @Location, se utiliza el nombre del campo para localizar la plantilla. En este caso particular, estamos inyectando una plantilla con la ruta templates/hello.txt. |
| 2 | Template.data() devuelve una nueva instancia de plantilla que se puede personalizar antes de que se active el renderizado real. En este caso, ponemos el valor del nombre bajo la clave name. El mapa de datos es accesible durante el renderizado. |
| 3 | Tenga en cuenta que nosotros no activamos la renderización - esto lo hace automáticamente una implementación especial de ContainerResponseFilter proporcionada por quarkus-rest-qute. |
Si su aplicación se está ejecutando, puede solicitar el punto final:
$ curl -w "\n" http://localhost:8080/hello?name=Martin
Hello Martin!
Plantillas con seguridad de tipos
Hay una forma alternativa de declarar sus plantillas en su código Java, que se basa en la siguiente convención:
-
Organice sus archivos de plantillas en el directorio
/src/main/resources/templates, agrupándolos en un directorio por cada clase de recurso. Así, si su claseFruitResourcehace referencia a dos plantillasapplesyoranges, colóquelas en/src/main/resources/templates/FruitResource/apples.txty/src/main/resources/templates/FruitResource/oranges.txt. Agrupar las plantillas por clase de recurso facilita la navegación hasta ellas. -
En cada una de sus clases de recursos, declare una clase
@CheckedTemplate static class Template {}dentro de su clase de recursos. -
Declare un
public static native TemplateInstance method();por archivo de plantilla para su recurso. -
Utilice esos métodos estáticos para construir sus instancias de plantilla.
Este es el ejemplo anterior, reescrito con este estilo:
Empezaremos con una plantilla muy sencilla:
Hello {name}! (1)
| 1 | {name} es una expresión de valor que se evalúa cuando se renderiza la plantilla. |
Ahora vamos a declarar y utilizar esta plantilla en la clase de 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;
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 | Esto declara una plantilla con la ruta templates/HelloResource/hello. |
| 2 | Templates.hello() devuelve una nueva instancia de plantilla que se devuelve desde el método del recurso. Tenga en cuenta que no activamos la renderización - esto lo hace automáticamente una implementación especial de ContainerResponseFilter proporcionada por quarkus-rest-qute. |
Una vez que haya declarado una clase @CheckedTemplate, comprobaremos que todos sus métodos apuntan a plantillas existentes, así que si intenta utilizar una plantilla de su código Java y se olvidó de añadirla, se lo haremos saber en el momento de la compilación :)
|
Tenga en cuenta que este estilo de declaración le permite referenciar plantillas declaradas en otros recursos también:
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);
}
}
Plantillas de nivel superior con seguridad de tipos
Naturalmente, si desea declarar plantillas en el nivel superior, directamente en /src/main/resources/templates/hello.txt, por ejemplo,
puede declararlas en una clase de nivel superior (no anidada) 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 | Esto declara una plantilla con la ruta templates/hello. |
Declaraciones de parámetros de plantillas
Si declara una declaración de parámetro en una plantilla, Qute intenta validar todas las expresiones que hacen referencia a este parámetro y si se encuentra una expresión incorrecta la compilación falla.
Supongamos que tenemos una clase simple como esta:
public class Item {
public String name;
public BigDecimal price;
}
Y nos gustaría renderizar una página HTML simple que contenga el nombre del artículo y el precio.
Empecemos de nuevo con la plantilla:
<!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 expresión está validada. Intente cambiar la expresión a {item.nonSense} y la compilación debería fallar. |
| 2 | Esto también se valida. |
Por último, vamos a crear una clase de recursos con plantillas con seguridad de tipos:
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 un método que nos da un TemplateInstance para templates/ItemResource/item.html y declare su parámetro Item item para que podamos validar la plantilla. |
| 2 | Haga que el objeto Item sea accesible en la plantilla. |
Cuando el argumento del compilador --parameters está habilitado, Quarkus REST puede inferir los nombres de los parámetros a partir de los nombres de los argumentos del método, haciendo que la anotación @PathParam("id") sea opcional en este caso.
|
Declaración de parámetros de la plantilla dentro de la propia plantilla
Alternativamente, puede declarar los parámetros de la plantilla en el propio archivo de la plantilla.
Empecemos de nuevo con la plantilla:
{@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 | Declaración opcional del parámetro. Qute intenta validar todas las expresiones que hacen referencia al parámetro item. |
| 2 | Esta expresión está validada. Intente cambiar la expresión a {item.nonSense} y la compilación debería fallar. |
Por último, vamos a crear una clase de recursos.
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 | Inyecte la plantilla con la ruta templates/item.html. |
| 2 | Haga que el objeto Item sea accesible en la plantilla. |
Métodos de extensión de plantillas
Los métodos de extensión de plantillas se utilizan para ampliar el conjunto de propiedades accesibles de los objetos de datos.
A veces, no tiene el control de las clases que desea utilizar en su plantilla y no puede añadirles métodos. Los métodos de extensión de plantillas le permiten declarar nuevos métodos para esas clases que estarán disponibles desde sus plantillas como si pertenecieran a la clase de destino.
Sigamos ampliando nuestra página HTML simple que contiene el nombre del artículo, el precio y añadamos un precio con descuento. El precio con descuento a veces se denomina "propiedad computada". Implementaremos un método de extensión de plantilla para renderizar esta propiedad fácilmente. Actualicemos nuestra plantilla:
<!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 es una sección de flujo de control básico. |
| 2 | Esta expresión también se valida contra la clase Item y, obviamente, no hay tal propiedad declarada. Sin embargo, hay un método de extensión de plantilla declarado en la clase TemplateExtensions - véase más abajo. |
Por último, vamos a crear una clase en la que pondremos todos nuestros métodos de extensión:
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 | Se puede utilizar un método de extensión de plantilla estática para añadir "propiedades computadas" a una clase de datos. La clase del primer parámetro se utiliza para hacer coincidir el objeto base y el nombre del método se utiliza para hacer coincidir el nombre de la propiedad. |
Puede colocar métodos de extensión de plantillas en cada clase si los anota con @TemplateExtension, pero le aconsejamos que los mantenga agrupados por tipo de destino o en una única clase TemplateExtensions por convención.
|
Renderizado de informes periódicos
El motor de plantillas también puede ser muy útil para renderizar informes periódicos.
Primero tendrá que añadir las extensiones quarkus-scheduler y quarkus-qute.
En su archivo pom.xml, añada:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-qute</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-scheduler</artifactId>
</dependency>
Supongamos que tenemos un bean SampleService cuyo método get() devuelve una lista de muestras.
public class Sample {
public boolean valid;
public String name;
public String data;
}
La plantilla es simple:
<!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 | La sección de bucle permite iterar sobre iterables, mapas y flujos. |
| 2 | Esta expresión de valor está utilizando el operador elvis - si el nombre es nulo se utiliza el valor por defecto. |
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 | En este caso, utilizamos el calificador @Location para especificar la ruta de la plantilla: templates/reports/v1/report_01.html. |
| 2 | Utilice la anotación @Scheduled para indicar a Quarkus que ejecute este método cada media hora. Para más información, consulte la guía del Programador. |
| 3 | El método TemplateInstance.render() desencadena la renderización. Tenga en cuenta que este método bloquea el hilo actual. |
Guía de referencia Qute
Para saber más sobre Qute, consulte la guía de referencia de Qute.
Referencia de configuración de Qute
Propiedad de configuración fijada en tiempo de compilación - Todas las demás propiedades de configuración son anulables en tiempo de ejecución
Configuration property |
Tipo |
Por defecto |
|---|---|---|
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 found in template roots. Excluded templates are neither parsed nor validated during build and are not available at runtime. The matched input is the file path relative from the root directory and 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:
By default, the 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 multiple templates with the same path are found in the application. 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 |
|
Enables or disables the Qute debug mode. This feature is experimental. When enabled, Qute templates can be debugged directly at runtime. This includes the ability to:
This mode is intended for development and troubleshooting purposes. Default value: Example configuration:
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 |
|