Qute テンプレートエンジン
Quteは、Quarkus のニーズを満たすために特別に設計されたテンプレートエンジンです。ネイティブイメージのサイズを小さくするために、リフレクションの使用は最小限に抑えられています。API は、命令型とノンブロッキングリアクティブ型の両方のスタイルのコーディングを組み合わせています。開発モードでは、 src/main/resources/templates にあるすべてのファイルが変更のために監視され、変更はすぐに表示されます。さらに、ビルド時にテンプレートの問題のほとんどを検出するようにしています。このガイドでは、アプリケーションでテンプレートを簡単にレンダリングする方法を学びます。
ソリューション
次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。
Gitレポジトリをクローンするか git clone https://github.com/quarkusio/quarkus-quickstarts.git 、 アーカイブ をダウンロードします。
このソリューションは qute-quickstart directory にあります。
JAX-RS で Hello World
JAX-RS アプリケーションで Qute を使用する場合は、まずエクステンションを追加する必要があります:
-
RESTEasy Reactive を使用している場合は
quarkus-resteasy-reactive-qute:pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-reactive-qute</artifactId> </dependency>build.gradleimplementation("io.quarkus:quarkus-resteasy-reactive-qute") -
RESTEasy Classicを使用している場合は、
quarkus-resteasy-qute:pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-qute</artifactId> </dependency>build.gradleimplementation("io.quarkus:quarkus-resteasy-qute")
まずはとてもシンプルなテンプレートから:
Hello {name}! (1)
| 1 | {name} は、テンプレートがレンダリングされたときに評価される値の式です。 |
デフォルトでは、 src/main/resources/templates ディレクトリーとそのサブディレクトリーにあるすべてのファイルがテンプレートとして登録されます。開発モードではテンプレートは起動時に検証され、変更が監視されます。
|
では、リソースクラスに「コンパイル済み」テンプレートを注入してみましょう。
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 | @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 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 | これは、パス templates/HelloResource/hello.txt でテンプレートを宣言します。 |
| 2 | Templates.hello() は、リソースメソッドから返される新しいテンプレートインスタンスを返します。 レンダリングをトリガーしないことに注意してください。これは、特別な `ContainerResponseFilter`実装によって自動的に行われます。 |
@CheckedTemplate クラスを宣言したら、すべてのメソッドが既存のテンプレートを指しているかどうかをチェックしますので、もし Java コードからテンプレートを使おうとして追加するのを忘れてしまった場合は、ビルド時にお知らせします :)
|
このスタイルの宣言では、他のリソースで宣言されたテンプレートも参照できることを覚えておいてください。
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);
}
}
トップレベルのタイプセーフテンプレート
当然のことながら、トップレベルでテンプレートを宣言したい場合、例えば /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 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 | templates/ItemResource/item.html に対して TemplateInstance を与えるメソッドを宣言し、その Item item パラメーターを宣言することで、テンプレートを検証することができます。 |
| 2 | テンプレート内で Item オブジェクトにアクセスできるようにします。 |
テンプレート内部のテンプレートパラメーター宣言
あるいは、テンプレートファイル自体でテンプレートパラメーターを宣言することもできます。
改めてテンプレートから始めてみましょう。
{@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 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 | パスが 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 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 | この場合、 @Location 修飾子を使用してテンプレートのパスを指定します: templates/reports/v1/report_01.html . |
| 2 | @Scheduled アノテーションを使用して、Quarkusにこのメソッドを30分毎に実行するよう指示します。詳細については、 スケジューラーガイド を参照してください。 |
| 3 | TemplateInstance.render() メソッドはレンダリングをトリガします。このメソッドは現在のスレッドをブロックすることに注意してください。 |
Qute リファレンスガイド
Qute について詳しく知りたい方は、 Qute リファレンスガイド を参考にしてください。
Qute 設定リファレンス
ビルド時に固定される設定プロパティ - 他のすべての設定プロパティは実行時にオーバーライド可能
タイプ |
デフォルト |
|
|---|---|---|
The list of suffixes used when attempting to locate a template file. By default, Environment variable: Show more |
文字列のリスト |
|
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 |
文字列のリスト |
|
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 |
文字列のリスト |
|
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 |
boolean |
|
If set to 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 |
|