Tapestry 5のコンポーネントクラスはTapestry 4のものよりとても簡単です。継承しなければならない基底クラスは無く、(抽象クラスではなく)具象クラスで、XMLファイルはありません。 依然、Javaアノテーションを用いてちょっとした設定は必要ですが、(Tapestry 4のように)抽象メソッドのgetter/setterではなくフィールドに直接アノテーションするようになりました。
ページ、コンポーネント、Mixinのクラスは全て同じ手順で作成します。
Tapestry 5のページやコンポーネントを作成するのはたやすい事です。
Tapestry 4と違い、Tapestry 5ではコンポーネントクラスは 抽象クラス ではなく、フレームワークが用意する基底クラスを継承することもありません。 コンポーネントクラスは純粋なPOJO(Plain Old Java Object)です。
コンポーネントクラスには少しの制約しかありません。
とても小さなコンポーネントを示します:
package org.example.myapp.components; import org.apache.tapestry5.MarkupWriter; import org.apache.tapestry5.annotations.BeginRender; public class HelloWorld { @BeginRender void renderMessage(MarkupWriter writer) { writer.write("Bonjour from HelloWorld component."); } }
このコンポーネントは固定のメッセージを出力するだけです。 BeginRenderアノテーションは コンポーネントのライフサイクルに関するアノテーションで、 あなたのクラスのメソッドがいつどういう状況で実行されるかをTapestryに指示するメソッドアノテーションです。
Tapestry 4とは異なるもうひとつの点は、これらのメソッドはパブリックである必要は無いということです; どのアクセス制御レベルでも好きなように使えます。
コンポーネントクラスは適切なパッケージ内にある必要があります(これはランタイムコード変換とクラスリロードのために必要なことです)。
これらのパッケージはアプリケーションのルートパッケージ下にあります。
ページは root.pages パッケージに配置します。ページ名はこのパッケージ内のクラスに対応付けられます。
コンポーネントは root.components パッケージに配置します。コンポーネントタイプはこのパッケージ内のクラスに対応付けられます。
Mixinは root.mixins パッケージに配置します。Mixinタイプはこのパッケージ内のクラスに対応付けられます。
また、アプリケーション内で基底クラスを使用するのは一般的なことでしょう。それらは、しばしば 抽象 基底クラスであり直接参照されるべきではありません。 これらを pages、components、mixins パッケージに入れてしまうと正規のページやコンポーネントやMixinに見えてしまうため、 そのようなクラスには root.base パッケージを使用してください。
(pages、components、mixinsなどの)パッケージ直下にクラスを置かなくても構いません。サブパッケージを作成しそこにクラスを配置する事もできます。 サブパッケージ名はページ名やコンポーネントタイプの一部になります。例えば、com.example.myapp.pages.admin.CreateUser というページを定義した場合、 (URL内にも現れる)ページの論理名は admin/CreateUser となります。
Tapestryは論理ページ名(やコンポーネントタイプ、Mixinタイプ)に簡単な最適化を行います。パッケージ名が非限定クラス名(もちろん、大文字小文字は区別しません)の接頭辞または接尾辞にもなっている場合は、 その接頭辞や接尾辞の部分を削除します。その結果、com.example.myapp.pages.user.EditUser というクラスのページ名は、(user/EditUser ではなく) user/Edit となります。 ここでの目標は、より短くより自然なURLを与えることです。
Tapestry 5におけるページとコンポーネントの違いはとてもとても僅かです。実際に異なる事といったらパッケージ名だけです: root.pages.PageName ならページであり、root.components.ComponentType ならコンポーネントというだけです。
Tapestry 4ではページとコンポーネントの間にもっと大きな違いがありました。 インターフェースもあなたのクラスが継承する抽象クラスの階層も分かれていました。
Tapestry 5内部で使用している "ページ(訳者注:Tapestry 4のようなページ)" というものはまだありますが、 ページコンポーネント(訳者注:Tapestry 5のページ)は単に、ページのコンポーネントツリーの ルートコンポーネント であるというだけです。
Tapestryはあなたのクラスを出発点として使用します。あなたのクラスを 変換 します。 これには、リクエストを跨いでページをプールする処理のためなどの多くの理由があります。
だいたいにおいて、この変換は道理にかなった処理を行うので気づく事はありません。 いくつかの特定の状況では、この変換は漏れのある抽象化 (訳者注:日本語による解説)となります -- 例えばインスタンス変数はプライベートである必要があります -- が、このプログラミングモデルは概して開発者のとても高い生産性を支えるでしょう。
変換は 実行時 まで行われないので、アプリケーションの構築時には影響を受けません。単体テスト時にはあなたのクラスはまったく単純なPOJOのままです。
コンポーネントクラスの変更をフレームワークが追跡します。クラスに変更があったときには再ロードされます。 これにより、Javaプラットフォームのパワーを犠牲にすることなく、スクリプト言語環境の軽快さでアプリケーションの開発ができます。
そして、それは速いのです! あなたはこの魔法のクラス再ロードが行われていることに気づかないことでしょう。
その結果は: すばらしい生産性 --- クラスを変更するとその変更内容をすぐに見ることができます。 これはJavaのスピードとパワーに(PythonやRubyのような)最高のスクリプティング環境を融合することを意図しています。
しかし、クラスの再ロードはコンポーネントクラスに対してのみ適用されます。 サービスインターフェースや実装やデータオブジェクトのようなその他のクラスは、通常のクラスローダによってロードされ動的なクラス再ロードの対象外となります。
Tapestryコンポーネントは(抽象プロパティを用いたTapestry 4とは異なり)インスタンス変数を持つことができます。
インスタンス変数はプライベートである必要があります。Tapestryはインスタンス変数をサポートするため実行時にクラス変換を行う必要がありますが、それはプライベート変数に対してのみ実行されます。 Tapestryはページとコンポーネントをプールし再利用するため、プライベートでないインスタンス変数があると予期せぬ動作を引き起こすことになるでしょう。 スタティックでもなくプライベートでもないフィールドがコンポーネントクラスに含まれているとTapestryはログにエラーを記録します。
インスタンス変数にアクセスするためには、getterメソッドとsetterメソッドを用意する必要があることに注意してください。 Property アノテーションをフィールドに与えない限り、Tapestryが自動で用意しては くれません。
アノテーションしていないインスタンス変数は、一時的な インスタンス変数です。 これは、リクエストが終わるとき(ページがリクエストから切り離されるとき)に変数の値がデフォルト値にリセットされるということです。
リクエストを超えて変数の値を維持する必要がありリセットロジックを止めたいときは、 そのフィールドにRetainアノテーションを添える必要があります。 あるページ インスタンス が後で別のユーザーのリクエストに使われるかもしれないので、クライアント毎のデータをそのフィールドに格納しないでください。 同様に、単一 ユーザーが後のリクエストでは 別の ページインスタンスを使用することもあります。
あるリクエストからその次のリクエストに情報を維持するには永続フィールドを使用してください。
(訳注:RetainアノテーションとPersistアノテーションの違いに注意してください)
また、finalフィールドは(実際に)ファイナルでありリセットされません。
Tapestryはデフォルトの引数無しコンストラクタを使ってあなたのクラスをインスタンス化します。その他のコンストラクタは無視されます。
依存関係の注入は、アノテーションによってフィールドレベルで行われます。 インジェクトされた値を持つフィールドは実行時にはリードオンリーとなっています。
Parameterアノテーションされたプライベートフィールドは、 コンポーネントパラメータとして認識されます。
リクエストを跨いで値を維持するためにフィールドをアノテーションすることができます。
(訳者注:こちらはPersistアノテーションです。Retainアノテーションとの違いに注意してください)
コンポーネントはしばしば他のコンポーネントを含んでいることがあります。コンポーネントテンプレート内にあるコンポーネントのことを 埋め込みコンポーネント と呼びます。 埋め込みコンポーネントを持つコンポーネントのテンプレートでは、Tapestry名前空間の要素を使って埋め込みコンポーネントを配置する場所を示します。
テンプレート内で埋め込みコンポーネントのタイプを指定することもできますし、 Componentアノテーションを使ってコンポーネントのタイプとパラメータを指定する事もできます。
例:
package org.example.app.pages; import org.apache.tapestry5.annotations.Component; import org.apache.tapestry5.annotations.Property; import org.example.app.components.Count; public class Countdown { @Component(parameters = { "start=5", "end=1", "value=countValue" }) private Count count; @Property private int countValue; }
上の例ではidが "count" の埋め込みコンポーネントを定義しています(このidはフィールド名を元に決定されます)。 コンポーネントの型は org.example.app.components.Count です。Countコンポーネントのstartとendパラメータはリテラル値にバインドされ、 valueパラメータはCountdownコンポーネントのcountValueプロパティにバインドされています。
Technically, the start and end parameters should be bound to properties, just the the value parameter. しかし、例にある数値リテラルのようなある種のリテラル値はプロパティではありませんが、 prop:バインディングプリフィックスでアクセスする事ができます(これはアプリケーション開発者の利便性のためです)。 "start=literal:5" と "literal:" プリフィックスを用いるのと同じ事です。
コンポーネントテンプレート内で他のパラメータを指定することもできますが、コンポーネントクラス内のパラメータが優先されます。
TODO: より複雑なチェックが必要になるかもしれません; テンプレート内でprop:を使用していてそれが矛盾していたら?
(フィールド名を元に決定された)デフォルトのコンポーネントidは、Componentアノテーションのidアトリビュートで上書きすることができます。
コンポーネントクラス内で定義されているコンポーネントに対応する要素がテンプレート内に無い場合はTapestryはエラーをログに記録します。