SeamFramework.orgCommunity Documentation

第4章 コンテキスト依存コンポーネントモデル

4.1. Seam コンテキスト
4.1.1. ステートレスコンテキスト
4.1.2. イベントコンテキスト
4.1.3. ページコンテキスト
4.1.4. 対話コンテキスト
4.1.5. セッションコンテキスト
4.1.6. ビジネスプロセスコンテキスト
4.1.7. アプリケーションコンテキスト
4.1.8. コンテキスト変数
4.1.9. コンテキスト検索優先順位
4.1.10. 同時並行処理モデル
4.2. Seam コンポーネント
4.2.1. ステートレスセッション Bean
4.2.2. ステートフルセッション Bean
4.2.3. エンティティ Bean
4.2.4. JavaBeans
4.2.5. メッセージ駆動型 Bean
4.2.6. インターセプション
4.2.7. コンポーネント名
4.2.8. コンポーネントスコープの定義
4.2.9. 複数ロールを持つコンポーネント
4.2.10. 組み込みコンポーネント
4.3. バイジェクション
4.4. ライフサイクルメソッド
4.5. 条件付きインストール
4.6. ロギング
4.7. Mutable インタフェースと @ReadOnly
4.8. ファクトリと管理コンポーネント

Seam における 2 つの中心的概念は、 コンテキスト の概念とコンポーネントの概念です。 コンポーネントは、ステートフルなオブジェクト、通常は EJB です。 コンポーネントのインスタンスは、コンテキストと関連づけられ、そのコンテキスト中で名前を与えられます。 バイジェクション (Bijection) は、内部のコンポーネント名 (インスタンス変数) をコンテキスト中の名前にエイリアスし、 Seam によるコンポーネントツリーの動的な組み立て、再組み立てを可能にするメカニズムを提供します。

Seam に組み込まれたコンテキストから説明を始めましょう。

Seam コンテキストはフレームワークによって生成、破棄されます。 アプリケーションは Java API 呼び出しによってコンテキスト区分 (demarcation) を明示的に制御することはできません。 コンテキストは通常、暗黙的ですが、場合によってコンテキストはアノテーションによって区分されます。

基本の Seam コンテキストは以下の通りです。

これらのコンテキストのいくつかは、サーブレットや関連する仕様書から由来していることがわかります。 しかし、このうち 2 つは目新しいかもしれません。 対話コンテキストビジネスプロセスコンテキストです。 WEB アプリケーション中での状態管理がとても脆弱でエラーが発生しやすい 1 つの理由は、 3 つの組み込みコンテキスト (リクエスト、セッション、アプリケーション) がビジネスロジックの観点から特定の意味を持たないからです。 例えば、実際のアプリケーションのワークフローの観点から見るとユーザログインセッションは極めて自由裁量な構造です。 そのため、ほとんどの Seam コンポーネントは、対話コンテキストあるいはビジネスプロセスコンテキストのスコープに配置されます。 なぜなら、それらはアプリケーションの観点からとても意味のあるコンテキストだからです。

順に、それぞれのコンテキストを見ていきましょう。

対話コンテキストは Seam でまさに中心となるコンセプトです。 対話 (conversation) は、ユーザの観点からの作業単位です。 それはユーザとのインタラクション、リクエスト、およびデータベーストランザクションをまたぐかもしれません。 しかし、ユーザにとって対話は、1 つの問題を解決します。 例えば、「ホテル予約」、「契約承認」、「注文作成」はすべて対話です。 対話というものが 1 つの「ユースケース」あるいは「ユーザストーリ」を実装していると考えたいかもしれませんが 関係は必ずしもその通りにはなりません。

対話は、「ユーザがこのウィンドウの中で現在していること」と関連づけられた状態を保持します。 1 人のユーザは、通常マルチウィンドウで、ある時点に進行中の複数の対話を持っているかもしれません。 対話コンテキストは、異なる対話からの状態の衝突をなくし、バグの原因とならないことを保証します。

対話の観点からアプリケーションについて考えることに慣れるには時間がかかるかもしれません。 しかし、慣れてしまうと、このコンセプトが大好きになり、もう対話なしでは考えられなくなるだろうと思います。

ある対話は単に 1 つのリクエストの間続いています。 複数のリクエストをまたぐ対話は、Seam によって提供されたアノテーションを使って、区分を示されなければなりません。

ある対話はまた タスク です。 タスクは長期ビジネスプロセスの観点では重要な意味を持つ対話であり、 タスクが首尾よく完了する場合、 ビジネスプロセスの状態遷移を引き起こす可能性を持っています。 Seam はタスク区分用に特別なアノテーションのセットを提供します。

より広い対話の "内部" で対話を発生させるような ネスト も可能です。 これは拡張機能です。

通常、実際にはリクエストとリクエストの間 servlet セッション中で Seam により対話状態は保持されます。 Seam は設定可能な 対話タイムアウト を実装し、 自動的に不活性な対話を破棄し、 ユーザが対話を中断しても、ユーザログインセッションにより保持される状態は際限なく増加しないことが保証されています。

Seam は同じプロセス中の同じ長期対話コンテキスト中で発生する並列のリクエスト処理をシリアル化します。

あるいは、Seam はクライアントブラウザの中に対話の状態を保持するように設定される場合もあります。

servlet 仕様も EJB 仕様も同じクライアントから起こる同時並行のリクエストを管理するための設備はまったく定義していません。 servlet コンテナは単純にすべてのスレッドを同時並行的に稼動させ、 スレッドセーフとすることをアプリケーションコードに任せます。 EJB コンテナはステートレスコンポーネントが同時並行的にアクセスされることを可能にし、 複数のスレッドがひとつのステートレスセッション Bean にアクセスするならば例外をスローします。

この振る舞いは粒度の細かい同期リクエストをベースとする古いスタイルの Web アプリケーションでは大丈夫であったかもしれません。 しかし、多くの粒度の細かい非同期リクエスト (AJAX) を多用する最新のアプリケーションのために、 同時並行はまぎれもない事実であり、プログラムモデルとしてサポートされなければなりません。 Seam は同時並行管理レイヤをコンテキストモデルに織り込みます。

Seam セッションとアプリケーションコンテキストはマルチスレッドになっています。 Seam は同時並行的に処理されるためにコンテキスト中での同時並行リクエストを許します。 イベントとページコンテキストは本来シングルスレッドです。 厳密に言えばビジネスプロセスコンテキストはマルチスレッドですが、 実際には同時並行はとてもまれで、この事実はほとんど着目されないかもしれません。 最後に、 同じ長期対話コンテキスト中の同時並行リクエストをシリアライズすることによって、 Seam は、対話コンテキストのために プロセスごと対話ごとのシングルスレッド モデルを実施します。

セッションコンテキストはマルチスレッドで、よく揮発性の状態を含むので、 そのコンポーネントのSeamインタセプタが無効にされていない限り、Seam によりセッションスコープコンポーネントは同時並行アクセスからいつも保護されています。 もしインタセプタが無効にされていたら、要求されるスレッドセーフ性はコンポーネント自身によって実装されなければなりません。Seam はデフォルトでリクエストを セッションスコープセッション Bean と JavaBean にシリアライズします。 ( そして、発生するどんなデッドロックも検出して打開します。) アプリケーションスコープのコンポーネントは通常揮発性の状態を保持しないため、 これはアプリケーションスコープのコンポーネントのためのデフォルトの振る舞いではありません。 なぜなら、グローバルレベルの同期化は 極端に コストがかかるからです。 しかし、 @Synchronized アノテーションを追加することで、 セッション Bean または JavaBean コンポーネントにシリアライズされたスレッドモデルを強制可能です。

この同時並行モデルは、 開発者側での特別な作業をまったく必要とすることなく、 AJAX クライアントが安全に揮発性セッションや対話状態を使用できることを意味します。

Seam コンポーネントは POJO (Plain Old Java Objects) です。 具体的には、Seam コンポーネントは JavaBean もしくは EJB 3.0 エンタープライズ Bean です。 Seam は コンポーネントが EJB が必須ではなく、また EJB 3.0 準拠のコンテナがなくても使用できますが、 Seam は EJB 3.0 を念頭にして設計され、EJB 3.0 と強く統合されています。 Seam は以下の コンポーネントタイプ をサポートします。

ステートレスセッション Bean コンポーネントは、複数の呼出しに対して状態を保持することができません。 従って、それらは通常さまざまな Seam コンテキスト内の別コンポーネントの状態を操作するのに役に立ちます。 それらは JSF のアクションリスナとして使用できるかもしれませんが、 表示のために JSF コンポーネントにプロパティを提供することはできません。

ステートレスセッション Bean はいつもステートレスコンテキストに置かれます。

新しいインスタンスが各リクエストで使用されるのと同様にステートレスセッション Bean は同時並行的にアクセスされることが可能です。 インスタンスをリクエストに割り当てることは EJB3 コンテナの責務です。 ( 通常インスタンスは再利用可能なプールから割り当てられます、 つまり、Bean の使用済みのものからデータを含むインスタンス変数を見つけることができまることを意味します。)

ステートレスセッション Bean は最も興味のわかない種類の Seam コンポーネントです。

Seam ステートレスセッション Bean コンポーネントは Component.getInstance() または @In(create=true) を使用してインスタンス化可能です。これらは JNDI ルックアップや 直接 new オペレータでインスタンス化されるべきではありません。

ステートフルセッション Bean コンポーネントは、 Bean の複数の呼出しに対して状態を保持することができるだけでなく、 複数のリクエストに対して状態を保持することもできます。 データベースに保持されていないアプリケーションの状態は、 通常、ステートフルセッション Bean によって保持される必要があります。 これは Seam と他の多くの WEB アプリケーションフレームワークとの大きな違いです。 現在の対話の情報を直接 HttpSession に押し込める代わりに、 対話コンテキストに結びついたステートフルセッション Bean のインスタンス変数の中にそれを保持すべきです。 これは、Seam がこの状態のライフサイクルの管理を可能にし、 異なる同時実行中の対話に関連する状態の間に衝突がないことを保証します。

ステートフルセッション Bean はしばしば JSF アクションリスナ、または、 表示もしくはフォームのサブミットのためにプロパティを提供する JSF コンポーネントのバッキング Bean として使用されます。

デフォルトで、ステートフルセッション Bean は対話コンテキストとバインドします。 それらはページもしくはステートレスコンテキストとバインドできません。

セッションスコープのステートレスセッション Bean への同時並行リクエストは、 そのBeanへのSeamインタセプタが無効にされていない限り、常に Seam によってシリアライズされます。

Seam ステートフルセッション Bean コンポーネントは Component.getInstance()) または @In(create=true) を使用してインスタンス化可能です。これらは JNDI ルックアップや 直接 new オペレータでインスタンス化されるべきではありません。

エンティティ Bean はコンテキスト変数とバインドし、Seamコンポーネントとして機能することもあります。 エンティティは、コンテキスト依存識別子に加えて永続識別子を持つために、 エンティティのインスタンスは、Seam によって暗黙的にインスタンス化されるより、 むしろ Java コード中で明示的にバインドされます。

エンティティ Bean コンポーネントはバイジェクションもコンテキスト区分もサポートしません。 また、エンティティ Bean トリガのデータ妥当性検証の呼び出しもサポートしていません。

エンティティ Bean は、通常 JSF アクションリスナとして使用されませんが、 しばしば、表示あるいはフォームのサブミットのために JSF コンポーネントにプロパティを提供するバッキング Bean として機能します。 特に、エンティティ Bean をバッキング Bean として使用することは一般的であり、 追加 / 変更 / 削除タイプの機能の実装のためのステートレスセッション Bean アクションリスナといっしょに使用されます。

デフォルトで、エンティティ Bean は対話コンテキストとバインドします。 ステートレスセッション Bean とはバインドしません。

クラスタリングされた環境では、 ステートフルセッション Bean 中でエンティティ Bean の参照を保持することより、 エンティティ Bean を直接的に対話あるいはセッションスコープの Seam コンテキスト変数にバインドする方が非効果的であることに留意してください。 この理由のため、すべての Seam アプリケーションが Seam コンポーネントであるためにエンティティ Bean を定義するわけではありません。

Seam エンティティ Bean コンポーネントは Component.getInstance() または @In(create=true) を使用してインスタンス化可能です。あるいは、直接 new オペレータを使用することが可能です。

すべての Seam コンポーネントは名前が必要です。 @Name アノテーションを使用してコンポーネントに名前を割り当てます。

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    ... 
}

この名前は、Seam コンポーネント名 (seam component name) で、 EJB 標準で定義された他の名前との関連はありません。 しかし、Seam コンポーネント名はちょうど JSF 管理 Bean のように動作するため、 2 つのコンセプトは同一と考えることができます。

@Name はコンポーネント名を定義する唯一の方法ではありませんが、 いつも、どこかで 名前を指定する必要があります。 もしそうしないと、他の Seam アノテーションはどれも機能しないでしょう。

ちょうど JSF のように、Seam コンポーネントインスタンスは、 通常コンポーネント名と同じ名前のコンテキスト変数と結合します。 従って、例えば、Contexts.getStatelessContext().get("loginAction") を使って、 LoginAction にアクセスできるでしょう。 具体的には、Seam 自身がコンポーネントをインスタンス化する時はいつでも、 それはコンポーネント名によって新しいインスタンスを変数と結合します。 しかし、この場合も JSF のように、 アプリケーションはプログラムに基づいた API コールによってコンポーネントを他のコンテキスト変数と結合させることも可能です。 特定のコンポーネントがシステムの中で複数のロールを提供する場合のみ、これは有用です。 例えば、現在のログイン UsercurrentUser セッションコンテキスト変数に結合されているかもしれませんが、 一方で、ある管理機能の主体である Useruser 対話コンテキスト変数に結合されているかもしれません。

非常に大規模なアプリケーションのために、そして組み込み Seam コンポーネントのために、修飾名はしばしば使われます。

@Name("com.jboss.myapp.loginAction")

@Stateless
public class LoginAction implements Login { 
    ... 
}

Java コード中でも JSF の式言語中でも修飾されたコンポーネント名は使用できます。


<h:commandButton type="submit" value="Login"
                 action="#{com.jboss.myapp.loginAction.login}"/>

これはうっとうしいので、Seam は修飾名を簡単な名前にエイリアスする機能も提供します。 以下のような行を components.xml ファイルに追加してください。


<factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>

すべての組み込み Seam コンポーネントは修飾名を持っていますが、 Seamの名前空間をインポートする機能によって非修飾名でもアクセスすることができます。Seam JARに含まれる components.xml ファイルは以下の名前空間を定義します。

<components xmlns="http://jboss.com/products/seam/components">
    
    <import>org.jboss.seam.core</import>
    <import>org.jboss.seam.cache</import>
    <import>org.jboss.seam.transaction</import>
    <import>org.jboss.seam.framework</import>
    <import>org.jboss.seam.web</import>
    <import>org.jboss.seam.faces</import>
    <import>org.jboss.seam.international</import>
    <import>org.jboss.seam.theme</import>
    <import>org.jboss.seam.pageflow</import>
    <import>org.jboss.seam.bpm</import>
    <import>org.jboss.seam.jms</import>
    <import>org.jboss.seam.mail</import>
    <import>org.jboss.seam.security</import>
    <import>org.jboss.seam.security.management</import>  
    <import>org.jboss.seam.security.permission</import>
    <import>org.jboss.seam.captcha</import>
    <import>org.jboss.seam.excel.exporter</import>
    <!-- ... --->
</components>

修飾された名前を解決するときは、Seamは順にそれぞれの名前空間を調べます。アプリケーション固有の名前空間のためにはアプリケーションのcomponents.xmlファイルに追加する名前空間を含めます。

依存性の注入 あるいは 制御の逆転 は今ではもう大多数の Java 開発者に親しい概念です。 依存性の注入はあるコンポーネントが他のコンポーネントの参照を可能にします。 それはコンテナに setter メソッドあるいはインスタンス変数に他のコンポーネントを「インジェクト (注入)」させることで実現します。 これまで見てきたすべての依存性の注入の実装では、 インジェクションはコンポーネントが生成されたときに起こり、 その後、参照はコンポーネントのライフサイクルの間で変化しません。 ステートレスコンポーネントにおいて、これは理にかなっています。 クライアントの観点から、特定のステートレスなコンポーネントのすべてのインスタンスは交換可能です。 一方、Seamはステートフルなコンポーネントの使用に重点を置いています。 従って、典型的な依存性の注入はもはやあまり有用な構造ではありません。 Seam はインジェクションの一般化として、バイジェクション (bijection) の概念を導入しました。 インジェクションと対照すると、バイジェクションは以下の通りです。

本質的に、インスタンス変数の値をインジェクト、アウトジェクト、両方により指定することで、 バイジェクションはコンテキスト変数をコンポーネントのインスタンス変数にエイリアスを可能にします もちろん、バイジェクションを可能にするためにアノテーションが使用されています。

@In アノテーションは値がインスタンス変数にインジェクトされることを指定しています。

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    @In User user;
    ... 
}

あるいは、setter メソッド

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    User user;
    
    @In
    public void setUser(User user) {
        this.user=user;
    }
    
    ... 
}

デフォルトでは、 Seam はプロパティ名あるいはインジェクトされたインスタンス変数名を使用して、 すべてのコンテキストの優先順位検索を行います。 例えば、 @In("currentUser")を使用することで明示的にコンテキスト変数を指定することもできます。

指定されたコンテキスト変数と関連した既存のコンポーネントインスタンスが存在しないときに、 Seam にコンポーネントのインスタンスの生成を望むならば、 @In(create=true) を指定する必要があります。 値がオプションで (null でも可能) であれば、@In(required=false) を指定してください。

いくつかのコンポーネントでは、 それらが使用されるところではどこでも繰り返し @In(create=true) を指定する必要があるかもしれません。 このような場合、 コンポーネントに @AutoCreate アノテーションを付けることが可能で、 create=true を明示的に使用しなくても、 必要なとき常に作成されるようになります。

式の値をインジェクトすることも可能です。

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    @In("#{user.username}") String username;
    ... 
}

注入された値はメソッドが終了しアウトジェクトされた後に逆方向に注入されます( 例えば null に設定されます )。

(コンポーネントライフサイクルとインジェクションについては次章により多くの情報があります。)

@Outアノテーションは、属性がインスタンス変数からもアウトジェクトされるべきことを指定します。

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    @Out User user;
    ... 
}

あるいは getter メソッドから

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    User user;
    
    @Out
    public User getUser() {
        return user;
    }
    
    ... 
}

属性値はインジェクトされることもアウトジェクトされることも可能です。

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    @In @Out User user;
    ... 
}

または、

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    User user;
    
    @In
    public void setUser(User user) {
        this.user=user;
    }
    
    @Out
    public User getUser() {
        return user;
    }
    
    ... 
}

セッション Bean とエンティティ Bean Seam コンポーネントは通常の EJB 3.0 のライフサイクルのコールバック (@PostConstruct@PreDestroy など) のすべてをサポートしています。 しかも、Seam は JavaBean コンポーネント でのこれらコールバックの使用もサポートしています。 しかし、これらのアノテーションは J2EE 環境では有効とならないため、Seam は @PostConstruct@PreDestroy と等価な 2 つの追加コンポーネントライフサイクルコールバックを定義しています。

@Create メソッドは Seam がコンポーネントをインスタンス化した後に呼ばれます。 コンポーネントは 1 つの @Createメソッドのみ定義可能です。

@Destroy メソッドは Seam コンポーネントがバインドするコンテキストが終了するときに呼ばれます。 コンポーネントは 1 つの @Destroy メソッドのみ定義可能です。

さらに、ステートフルセッション Bean コンポーネントはパラメータ無しの@Removeを付けることが必須 です。このメソッドはコンテキストが終了するときに Seam により呼ばれます。

最後に、関連するアノテーションは @Startup アノテーションです。 それはアプリケーションやセッションスコープコンポーネントで利用可能です。 @Startup アノテーションは、 コンテキストが開始されたときにクライアントによる初めての参照を待つのではなく、 Seam に即座にコンポーネントをインスタンス化させさせます。 @Startup(depends={....}) を指定することで、 スタートアップコンポーネントのインスタンス化する順序の制御が可能です。

@Install アノテーションは、 特定のデプロイメントシナリオでは必須で別の場合はそうでないようなコンポーネントの条件付インストレーションを可能にします。 これは以下の場合に便利です。

@Install は works by letting you specify 優先順位依存性 を指定することで動作します。

コンポーネントの優先順位は、 クラスパス中に同じコンポーネント名を持つ複数のクラスがある場合に、 インストールすべきコンポーネントを決定するために Seam が使用する番号です。 Seam はより優先湯順位が高いコンポーネントを選択します。 あらかじめ決められた優先順位の値があります (昇順)。

JMS キューと対話する messageSender という名前のコンポーネントがあるとします。

@Name("messageSender") 

public class MessageSender {
    public void sendMessage() {
        //do something with JMS
    }
}

ユニットテストで、 有効なJMS キューがないので、このメソッドを消してしまいたくなります。 ユニットテストが実行されるときにクラスパスに存在するけれどアプリケーションではデプロイされない mock コンポーネントを作成します。

@Name("messageSender") 

@Install(precedence=MOCK)
public class MockMessageSender extends MessageSender {
    public void sendMessage() {
        //do nothing!
    }
}

優先順位 はクラスパスで両方のコンポーネントを発見したとき、 Seam がどちらのバージョンを使用するかを助けます。

クラスパスにある複数のクラスを正確に制御できるならば、 これはすばらしいことです。 しかし、多くの依存性を持つ再利用可能なフレームワークを記述している場合、 多くの Jar 全体にそのフレームワークをブレークさせたいとは思わないでしょう。 他にどのようなコンポーネントがインストールされているか、クラスパス中にどんなクラスが使用可能であるかに応じて、 インストールすべきコンポーネントを決める方法の方が好まれるはずです。 @Install アノテーションはこの機能も制御しています。 Seam は多くの組み込みコンポーネントの条件付きインストールを実現するために内部でこのメカニズムを使用します。 しかし、アプリケーションでは恐らく使用する必要がないでしょう。

こんなうっとうしいコードを見るのに飽き飽きしているのは誰ですか?

private static final Log log = LogFactory.getLog(CreateOrderAction.class);

        
public Order createOrder(User user, Product product, int quantity) {
    if ( log.isDebugEnabled() ) {
        log.debug("Creating new order for user: " + user.username() + 
            " product: " + product.name() 
            + " quantity: " + quantity);
    }
    return new Order(user, product, quantity);
}

簡単なログメッセージのためのコードをどうしてこんなに冗長にすることができるのか想像するのは困難です。 実際のビジネスロジックに関連するコード行よりロギングに関連する方がより多くあります。 Java コミュニティが 10 年の間もっと良いものを考え出せなかったことは本当に驚きです。

Seam はたくさんのコードを簡素化するロギング API を提供します。

@Logger private Log log;

        
public Order createOrder(User user, Product product, int quantity) {
    log.debug("Creating new order for user: #0 product: #1 quantity: #2", user.username(), product.name(), quantity);
    return new Order(user, product, quantity);
}

log 変数が静的であると宣言するかどうかは問題ではありません — log 変数が静的である必要があるエンティティ Bean コンポーネント以外なら、 どちらの方法でもうまくいくでしょう。

ストリング連結は、debug() メソッドの 内部 で起こるため、 うっとうしい if ( log.isDebugEnabled() ) による監視は不要であることに留意してください。 Seam はどのコンポーネントに Log をインジェクトしたかを知っているため、 通常、ログカテゴリを明示的に指定する必要ないことも留意してください。

UserProduct が、 現在のコンテキストで有効な Seam コンポーネントの場合、それはさらに良くなります。

@Logger private Log log;

        
public Order createOrder(User user, Product product, int quantity) {
    log.debug("Creating new order for user: #{user.username} product: #{product.name} quantity: #0", quantity);
    return new Order(user, product, quantity);
}

Seam ロギングは自動的に log4j あるいは JDK logging に出力を送付するかを選択します。 log4j がクラスパスに通っていれば、Seam はそれを使用します。 そうでなければ、Seam は JDK logging を使用します。

アプリケーションが明示的に setAttribute() を呼び出すとときに、 セッションにバインドした可変オブジェクトの状態変化が複製されるだけなので、 多くのアプリケーションサーバの機能は驚くほどいい加減な HttpSession クラスタリングの実装を持っています。 これはフェイルオーバが発生するときにだけに現れるので、 効果的に開発時間でテストされることができないバグの原因です。 さらに実際の複製メッセージはセッション属性とバインドしたシリアライズされたオブジェクトグラフ全体を含んでいます。 そして、それは非効率です。

もちろん、EJB ステートフルセッション Bean は自動的にダーティなチェックを実行が必要であり、 可変状態の複製と洗練された EJB コンテナは属性レベルの複製など最適化が行うことが可能です。 あいにく、すべての Seam ユーザが EJB 3.0 をサポートする恵まれた環境で作業をしているわけではありません。 そこで、セッションと対話スコープの JavaBean とエンティティ Bean コンポーネントのために、 Seam は Web コンテナセッションクラスタリングの上でクラスタセーフな状態管理の特別なレイヤを提供します。

セッションや対話スコープの JavaBean コンポーネントのために、Seam は、 コンポーネントがアプリケーションにより呼び出されるリクエストの毎に、 setAttribute() を呼ぶことにより自動的に複製を命じます。 もちろん、このストラテジは読み取りばかりするコンポーネントでは不十分です。 この振る舞いは、 org.jboss.seam.core.Mutable インタフェースを実装するか、 org.jboss.seam.core.AbstractMutable を拡張するか、 あるいは、コンポーネント中に独自のダーティチェックのロジックを記述するかにより制御可能です。 以下に例を示します。

@Name("account")

public class Account extends AbstractMutable
{
    private BigDecimal balance;
    
    public void setBalance(BigDecimal balance)
    {
        setDirty(this.balance, balance);
        this.balance = balance;
    }
    
    public BigDecimal getBalance()
    {
        return balance;
    }
    
    ...
    
}

あるいは、同様の効果を得るために @ReadOnly アノテーションの使用も可能です。

@Name("account")

public class Account
{
    private BigDecimal balance;
    
    public void setBalance(BigDecimal balance)
    {
        this.balance = balance;
    }
    
    @ReadOnly
    public BigDecimal getBalance()
    {
        return balance;
    }
    
    ...
    
}

セッションや対話スコープのエンティティ Bean コンポーネントの場合、 Seam は 複製が不要な場合、 ( 対話スコープの) エンティティが現在の Seam 管理の永続性コンテキストに関連付けられている限り、 リクエスト毎に setAttribute() を呼ぶことにより自動的に複製の作成を強制します。 このストラテジは必ずしも効率的ではないので、 セッションや対話スコープエンティティ Bean は注意して使用してください。 エンティティ Bean インスタンスを「管理」するために、 ステートフルセッション Bean や JavaBean をいつでも記述することができます。 以下に例を示します。

@Stateful

@Name("account")
public class AccountManager extends AbstractMutable
{
    private Account account; // an entity bean
    
    @Unwrap
    public Account getAccount()
    {
        return account;
    }
    
    ...
    
}

Seam アプリケーションフレームワークにおいて EntityHome クラスは Seam コンポーネントを使用することでエンティティ Bean インスタンスを管理する優れたサンプルを提供していることに留意してください。

Seam コンポーネントではないオブジェクトと連携することもしばしばあります。 でも、やはり @In を使用して Seam コンポーネントにインジェクトし、 値やメソッドバインディング式などでそれらを使いたいと思うことがあります。 時には、それを Seam コンテキストのライフサイクルに関連付ける必要さえあります (例えば @Destroy)。 そこで、Seam コンテキストは Seam ではないいオブジェクトを含むことが可能で、 Seam は、コンテキストにバインドする非コンポーネントと連携することを容易にする 2、3 の優れた機能を提供します。

ファクトリコンポーネントパターン は、 Seam コンポーネントを非コンポーネントオブジェクト用のインスタンス化する機能として動作させます。 ファクトリメソッド は、 コンテキスト変数が参照されたときに呼び出されますが、 それとバインドした値は持っていません。 @Factory アノテーションを使用してファクトリメソッドを定義します。 ファクトリメソッドは値をコンテキスト変数とバインドし、 バインドされた値のスコープを決定します。 2 種類のファクトリメソッドスタイルがあります。 最初のスタイルは、Seam によりコンテキストにバインドされた値を返します。

@Factory(scope=CONVERSATION)

public List<Customer
> getCustomerList() { 
    return ... ;
} 

2 番目のスタイルは、 値をコンテキスト変数そのものにバインドした void タイプのメソッドです。

@DataModel List<Customer

> customerList;
@Factory("customerList")
public void initCustomerList() { 
    customerList = ...  ;
} 

どちらの場合も、 customerList コンテキスト変数を参照してその値が null になり、 その値のライフサイクルで行うことがこれ以上ない場合、 ファクトリメソッドが呼ばれます。 さらに強力なパターンは 管理コンポーネントパターン です。 この場合、 コンテキスト変数にバインドする Seam コンポーネントがあり、 このコンポーネントがコンテキスト変数の値を管理り、 残りはクライアントで見えない場合です。

管理コンポーネントは @Unwrap メソッドを持つすべてのコンポーネントです。 このメソッドは、クライアントに見えなくなる値を返し、 毎回 コンテキスト変数が参照されれば呼び出されます。

@Name("customerList")

@Scope(CONVERSATION)
public class CustomerListManager
{
    ...
    
    @Unwrap
    public List<Customer
> getCustomerList() { 
        return ... ;
    }
}

マネージャコンポーネントパタンはコンポーネントのライフサイクルでより制御を必要とする場面にオブジェクトがあるとき特に有用です。 例えば、コンテキスト終了時にクリーンアップを必要とする重量級のオブジェクトがあるとき、オブジェクトを @Unwrap し、マネージャコンポーネントの @Destroy メソッドでクリーンアップすることが可能です。

@Name("hens")

@Scope(APPLICATION) 
public class HenHouse {
    
    Set<Hen
> hens;
    
    @In(required=false) Hen hen;
    
    @Unwrap
    public List<Hen
> getHens() {
        if (hens == null) {
            // Setup our hens
        }
        return hens;
    }
    
    @Observer({"chickBorn", "chickenBoughtAtMarket"})
    public addHen() {
        hens.add(hen);
    }
    
    @Observer("chickenSoldAtMarket")
    public removeHen() {
        hens.remove(hen);
    }
    
    @Observer("foxGetsIn")
    public removeAllHens() {
        hens.clear();
    }
    ...
} 

ここでは管理コンポーネントが基礎をなすオブジェクトの多くのイベント監視をしています。 コンポーネントはこれらのアクションそのものを管理し、オブジェクトはアクセスごとにアンラップされるために一貫性のあるビューが提供されます。