Qute テンプレートエンジン
Quteは、Quarkus のニーズを満たすために特別に設計されたテンプレートエンジンです。ネイティブイメージのサイズを小さくするために、リフレクションの使用は最小限に抑えられています。API は、命令型とノンブロッキングリアクティブ型の両方のスタイルのコーディングを組み合わせています。開発モードでは、 src/main/resources/templates
にあるすべてのファイルが変更のために監視され、変更はすぐに表示されます。さらに、ビルド時にテンプレートの問題のほとんどを検出するようにしています。このガイドでは、アプリケーションでテンプレートを簡単にレンダリングする方法を学びます。
ソリューション
次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。
Gitリポジトリをクローンする: git clone https://github.com/quarkusio/quarkus-quickstarts.git
、または archive をダウンロードする。
ソリューションは 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} は、テンプレートがレンダリングされたときに評価される値の式です。 |
では、リソースクラスに「コンパイル済み」テンプレートを注入してみましょう。
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 | @Location 修飾子が指定されていない場合は、フィールド名がテンプレートの場所を特定するために使用されます。このケースでは、パスが templates/hello.txt であるテンプレートを注入しています。 |
2 | Template.data() は、実際のレンダリングがトリガーされる前にカスタマイズできる新しいテンプレートインスタンスを返します。この場合、名前の値をキー name の下に置きます。データマップはレンダリング中にアクセス可能です。 |
3 | レンダリングをトリガーしないことに注意してください - これは特別な ContainerResponseFilter の実装によって自動的に行われます。 |
アプリケーションが動作している場合は、エンドポイントを要求することができます:
$ curl -w "\n" http://localhost:8080/hello?name=Martin
Hello Martin!
タイプセーフテンプレート
Java コードでテンプレートを宣言する別の方法もあり、以下の規約に依存しています。
-
テンプレートファイルを
/src/main/resources/templates
ディレクトリーに整理し、リソースクラスごとに 1 つのディレクトリーにグループ化します。つまり、ItemResource
クラスがhello
とgoodbye
の 2 つのテンプレートを参照している場合は、/src/main/resources/templates/ItemResource/hello.txt
と/src/main/resources/templates/ItemResource/goodbye.txt
に配置します。リソースクラスごとにテンプレートをグループ化することで、テンプレートへのナビゲートが容易になります。 -
各リソースクラスで、リソースクラス内で
@CheckedTemplate static class Template {}
クラスを宣言します。 -
リ ソ ース のテ ン プ レー ト フ ァ イ ルごとに
public static native TemplateInstance method();
を 1 つずつ宣言 し ます。 -
テンプレートインスタンスを構築するには、これらの静的メソッドを使用します。
先ほどの例を、このスタイルで書き換えてみました:
まずはとてもシンプルなテンプレートから:
Hello {name}! (1)
1 | {name} は、テンプレートがレンダリングされたときに評価される値の式です。 |
では、これらのテンプレートをリソースクラスで宣言して使ってみましょう。
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 | これは、パス templates/HelloResource/hello.txt でテンプレートを宣言します。 |
2 | Templates.hello() は、リソースメソッドから返される新しいテンプレートインスタンスを返します。 レンダリングをトリガーしないことに注意してください。これは、特別な `ContainerResponseFilter`実装によって自動的に行われます。 |
@CheckedTemplate クラスを宣言したら、すべてのメソッドが既存のテンプレートを指しているかどうかをチェックしますので、もし Java コードからテンプレートを使おうとして追加するのを忘れてしまった場合は、ビルド時にお知らせします :)
|
このスタイルの宣言では、他のリソースで宣言されたテンプレートも参照できることを覚えておいてください。
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);
}
}
トップレベルのタイプセーフテンプレート
当然のことながら、トップレベルでテンプレートを宣言したい場合、例えば /src/main/resources/templates/hello.txt
で直接宣言したい場合は、トップレベル(非入れ子)の 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 | これは、パス templates/hello.txt でテンプレートを宣言します。 |
テンプレートパラメーター宣言
テンプレートで パラメーター宣言 を宣言した場合、Quteはこのパラメーターを参照するすべての式の検証を試み、間違った式が見つかった場合はビルドが失敗します。
このようなシンプルなクラスがあったとしましょう:
public class Item {
public String name;
public BigDecimal price;
}
そして、商品名と価格が記載されたシンプルな HTML ページをレンダリングしたいと思います。
改めてテンプレートから始めてみましょう。
<!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 | この式は検証済みです。式を {item.nonSense} に変更するとビルドに失敗します。 |
2 | これも検証されています。 |
最後に、タイプセーフなテンプレートを使ってリソースクラスを作ってみましょう。
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 | templates/ItemResource/item.html に対して TemplateInstance を与えるメソッドを宣言し、その Item item パラメーターを宣言することで、テンプレートを検証することができます。 |
2 | テンプレート内で Item オブジェクトにアクセスできるようにします。 |
--parameters コンパイラ引数が有効な場合、Quarkus REST はメソッドの引数名からパラメータ名を推測することができるため、この場合 @PathParam("id") アノテーションはオプションになります。
|
テンプレート内部のテンプレートパラメーター宣言
あるいは、テンプレートファイル自体でテンプレートパラメーターを宣言することもできます。
改めてテンプレートから始めてみましょう。
{@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 | オプションのパラメーター宣言。Qute は、パラメーター item を参照するすべての式の検証を試みます。 |
2 | この式は検証済みです。式を {item.nonSense} に変更するとビルドに失敗します。 |
最後に、リソースクラスを作成してみましょう。
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 | パスが templates/item.html のテンプレートを注入します。 |
2 | テンプレート内で Item オブジェクトにアクセスできるようにします。 |
テンプレート拡張メソッド
テンプレート拡張メソッド は 、データオブジェクトのアクセス可能なプロパティーのセットを拡張するために使用されます。
時には、テンプレートで使用したいクラスをコントロールできず、それらのクラスにメソッドを追加できないことがあります。テンプレート拡張メソッドを使うと、テンプレートから利用できるようになるクラスのメソッドを、あたかもターゲットクラスに属しているかのように宣言することができます。
アイテム名、価格、値引き価格を含むシンプルな HTML ページを拡張していきましょう。値引き価格は「計算プロパティー」と呼ばれることがあります。このプロパティーを簡単にレンダリングするためのテンプレート拡張メソッドを実装します。テンプレートを更新してみましょう。
<!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 は、基本的な制御フローのセクションです。 |
2 | この式は Item クラスに対しても検証されており、明らかにそのようなプロパティーは宣言されていません。しかし、 TemplateExtensions クラスにはテンプレート拡張メソッドが宣言されています - 以下を参照してください。 |
最後に、すべての拡張メソッドを置くクラスを作りましょう。
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 | 静的テンプレート拡張メソッドを使用して、データクラスに「計算プロパティー」を追加することができます。最初のパラメーターのクラスはベースオブジェクトと一致するように使用され、メソッド名はプロパティー名と一致するように使用されます。 |
テンプレート拡張メソッドは、 @TemplateExtension でアノテーションを付ければ、すべてのクラスに配置することができますが、規約上、ターゲットタイプでグループ化するか、 TemplateExtensions クラスにまとめておくことをお勧めします。
|
定期レポートのレンダリング
テンプレーティングエンジンは、定期的なレポートをレンダリングする際にも非常に便利かもしれません。最初に quarkus-scheduler
と quarkus-qute
のエクステンションを追加する必要があります。 pom.xml
ファイルに追加します。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-qute</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-scheduler</artifactId>
</dependency>
SampleService
Beanがあり、その get()
メソッドがサンプルのリストを返すとしましょう。
public class Sample {
public boolean valid;
public String name;
public String data;
}
テンプレートはシンプルです:
<!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 | ループセクションでは、イテレーション、マップ、ストリームの反復処理が可能になります。 |
2 | この値式は elvis 演算子 を使用します - nameが NULL の場合はデフォルト値が使用されます。 |
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 | この場合、 @Location 修飾子を使用してテンプレートのパスを指定します: templates/reports/v1/report_01.html . |
2 | @Scheduled アノテーションを使用して、Quarkusにこのメソッドを30分毎に実行するよう指示します。詳細については、 スケジューラーガイド を参照してください。 |
3 | TemplateInstance.render() メソッドはレンダリングをトリガします。このメソッドは現在のスレッドをブロックすることに注意してください。 |
Qute リファレンスガイド
Qute について詳しく知りたい方は、 Qute リファレンスガイド を参考にしてください。
Qute 設定リファレンス
ビルド時に固定される構成プロパティ - 他のすべての構成プロパティは実行時にオーバーライド可能
Configuration property |
型 |
デフォルト |
---|---|---|
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 |
長 |
|
If set to Environment variable: Show more |
boolean |
|