訳注: 原文では Environmental service と Environment service というふたつの語を区別して使っていますが、 紛らわしいのでここでは Environmental service -> 環境オブジェクトEnvironment service -> Environmentサービス と訳しています。Environment インターフェースを実装したIoCサービスがEnvironmentサービスで、その中で管理されているものが環境オブジェクトです。

環境オブジェクト

環境オブジェクトはインジェクションのもうひとつの形です

サービスへのインジェクション(サービス実装のコンストラクタへのインジェクション)や コンポーネントへの通常のインジェクション(@Injectアノテーションによるフィールドへのインジェクション)では、 インジェクトされた値は常に同じです。 一方、環境オブジェクトはインジェクトされるタイミングがとても遅くそしてその値は動的です。

環境オブジェクトは、あるコンポーネントとその内部に含まれるコンポーネントをつなぐコンジットチューブ(電線管)のようなものです。

一例としてフォームが挙げられます; Form コンポーネントは FormSupport 型の 環境オブジェクトを作成します。FormSupportインターフェースは、 フォームのレンダリング時やサブミット時にフォームとその内部のコンポーネントが協調し合うことを可能にします。 フォーム内部の要素名やクライアント側のidがどのように決定されるのかを制御し、 サブミトッ時にフィールドの値を処理するコールバックを登録する手段を提供し、 フィールドのクライアント側入力検証をフックする手段を提供します。

@Environmentalアノテーションの使用

Environmental アノテーションは、外側のコンポーネントによって提供されている環境オブジェクトに動的に接続するために使用します。

とてもよく使う環境オブジェクトのひとつが RenderSupport です。 これはクライアント側のJavaScriptを生成するために使います。

  @Inject @Path("${tapestry.scriptaculous}/dragdrop.js")
  private Asset dragDropLibrary;

  @Environmental
  private RenderSupport renderSupport;

  void setupRender()
  {
    renderSupport.addScriptLink(dragDropLibrary);
  }

環境オブジェクトはその性質上、スレッド毎(それゆえリクエスト毎)に割り当てられます。

Environmentalアノテーションされたフィールドにアクセスすると、 そのフィールドの型で Environment サービスに対して検索が行われます。

そして、その環境オブジェクトがEnvironmentサービス内に存在しなかった場合、通常は例外が投げられます。

Environmentalアノテーションの値をfalseにすると、その環境オブジェクトはオプションとなります。

Environmentサービスに環境オブジェクトを追加する

Environmentサービスは push() メソッドと pop() メソッドを持っており、 環境オブジェクトをEnvironmentサービスへ追加したり取り除いたりできます。

例えば、タブベースのメニューシステムを構築する場合を考えます。 外側のコンポーネントであるTabGroupコンポーネントがその内部にあるTabコンポーネントと連携し、外観を制御する必要があるでしょう。

そのために必要な情報をTabModelインターフェースを通して伝えます。

public class TabGroup
{
  @Inject
  private Environment environment;

  void beginRender()
  {
     environment.push(TabModel.class, new TabModelImpl(...));
  }

  void afterRender()
  {
    environment.pop(TabModel.class);
  }
}

public class Tab
{
  @Environmental
  private TabModel model;

  void beginRender(MarkupWriter writer)
  {
    ...
  }
}

Environmentサービスに環境オブジェクトを追加する際に、そのインスタンスとともに型を指定します。 Environmentサービスは複数のスタックを管理しており、型毎にひとつのスタックがあります。 そのため、TabModelのインスタンスをEnvironmentサービスに追加しても、RenderSupportや他の既存の環境オブジェクトに影響することはありません。

ここで重要なことは、環境オブジェクトをスタックに追加したコードにはスタックから取り除く責任があるということです。

内部のTabクラスは、TabGroupによってスタックに追加されたオブジェクトに自由にアクセスすることができます。

Environmentサービスがスタックとなっている理由は、 環境オブジェクトを差し替えたり環境オブジェクトへのアクセスに割り込んだりすることを容易にできるようにするためです。

基本的な環境オブジェクト

全ての環境オブジェクトがコンポーネントによって追加されるわけではありません。

いくつかの環境オブジェクトは最初のコンポーネントのレンダリングが始まる前に初期化されます。この初期化は MarkupRenderer サービスに登録された MarkupRendererFilter によって行われます。

サービス内からの環境オブジェクトへのアクセス

Environmenalアノテーションはコンポーネント内でのみ使うことができます。

サービス実装内で環境オブジェクトにアクセスするためには、Environmentサービスをサービスにインジェクトし、peek() メソッドを使って環境オブジェクトを取り出さなければなりません。

これを頻繁に行わなければならない場合は、Environmentサービスを "背後に持つ" サービス実装を作ることができます。 例えば、RenderSupportはTapestryModule内で次のように構築されているので、通常のインジェクションでアクセスすることができます:

  public RenderSupport buildRenderSupport(EnvironmentalShadowBuilder builder)
  {
    return builder.build(RenderSupport.class);
  }

EnvironmentShadowBuilderサービスは、Environmentサービス内にある適切なインスタンスへ委譲するサービス実装を生成します。 この方法はあなたが作成した環境オブジェクトに用いることもできます。