Motor de plantillas Qute
Qute is a templating engine designed specifically to meet the Quarkus needs.
The usage of reflection is minimized to reduce the size of native images.
The API combines both the imperative and the non-blocking reactive style of coding.
In the development mode, all files located in src/main/resources/templates are watched for changes and modifications are immediately visible.
Furthermore, we try to detect most of the template problems at build time.
In this guide, you will learn how to easily render templates in your application.
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 -b 3.8 https://github.com/quarkusio/quarkus-quickstarts.git o descargue un archivo.
La solución se encuentra en el qute-quickstart directorio.
Hello World with Jakarta REST
If you want to use Qute in your Jakarta REST application, you need to add an extension first:
-
either
quarkus-resteasy-reactive-quteif you are using RESTEasy Reactive:pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-reactive-qute</artifactId> </dependency>build.gradleimplementation("io.quarkus:quarkus-resteasy-reactive-qute") -
or
quarkus-resteasy-quteif you are using RESTEasy Classic:pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-qute</artifactId> </dependency>build.gradleimplementation("io.quarkus:quarkus-resteasy-qute")
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. |
By default, all files located in the src/main/resources/templates directory and its subdirectories are registered as templates. Templates are validated during startup and watched for changes in the development mode.
|
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 | Note that we don’t trigger the rendering - this is done automatically by a special ContainerResponseFilter implementation. |
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:
-
Organise your template files in the
/src/main/resources/templatesdirectory, by grouping them into one directory per resource class. So, if yourItemResourceclass references two templateshelloandgoodbye, place them at/src/main/resources/templates/ItemResource/hello.txtand/src/main/resources/templates/ItemResource/goodbye.txt. Grouping templates per resource class makes it easier to navigate to them. -
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. |
Now let’s declare and use those templates in the resource class.
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() returns a new template instance that is returned from the resource method. Note that we don’t trigger the rendering - this is done automatically by a special ContainerResponseFilter implementation. |
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("goodbye")
public class GoodbyeResource {
@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. |
When the --parameters compiler argument is enabled, RESTEasy Reactive may infer the parameter names from the method argument names, making the @PathParam("id") annotation optional in this case.
|
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.
Sometimes, you’re not in control of the classes that you want to use in your template, and you cannot add methods to them. Template extension methods allows you to declare new method for those classes that will be available from your templates just as if they belonged to the target class.
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
Templating engine could be also very useful when rendering periodic reports.
You’ll need to add the quarkus-scheduler and quarkus-qute extensions first.
In your pom.xml file, add:
<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
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 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 |
||
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 |
|