ZK での CDI プログラミング・モデルを探る

単純なアプリケーションを実装する

JSR (Java™ Specification Request) 299: CDI (Contexts and Dependency Injection) for the Java EE platform では、強力なサービス一式を定義しています。これらのサービスには、タイプ・セーフな Java EE コンポーネントの依存性注入とコンポーネント間での対話を可能にするイベント通知モデルが含まれていて、Java EE Web 層から簡単に Java EE サービスにアクセスできるようになります。基本的に、移植可能な CDI エクステンション・メカニズムを使用することで、Java EE Web 層で使用されているあらゆるサード・パーティーのフレームワークから CDI サービスを利用することができます。この記事では、「ZK を使ったリッチ・インターネット・アプリケーション」のサンプル・アプリケーションを拡張する手順を通して、ZK フレームワークと、その強力な CDI サービスとの統合によって実際のサンプル・アプリケーションを変更する方法を説明します。

Mr. Sachin K Mahajan, Software Developer, WSO2 Inc

Photo of Sachin MahajanSachin はユタ州のソルトレーク・シティーにある University of Utah の修士号を取得しています。彼は米国とインドの中小企業や大企業で働いたことがあり、技術者として、また監督者として多様な業務を行ってきました。彼は現在、IBM Software Group の Lotus 部門で働いています。


developerWorks 貢献著者レベル

Ashish Dasnurkar, Engineer, Potix Corp.

Photo of Ashish DasnurkarAshish はインドの University of Pune で学士号を取得した後、7 年間にわたって多国籍企業向けエンタープライズ・アプリケーションでの Java EE プラットフォームに取り組んできました。現在は、ZK Ajax フレームワークの影の推進力となっている Potix Corporation に勤務しています。



2010年 5月 25日

はじめに

ZK は、Java コードで作成されたオープンソースの Ajax (Asynchronous JavaScript and XML) フレームワークです。ZK では、JavaScript コードを1 行も作成せずに Web 2.0 対応のリッチ・インターネット・アプリケーションを作成することができます。

ZK はいわば、JavaScript 抜きの Ajax のようなものです。この強力なフレームワークは、Ajax ベースのイベント駆動型エンジン、豊富な XHTML コンポーネントと XUL コンポーネント、そして ZUML という、機能豊富なユーザー・インターフェースを作成するためのマークアップ言語で構成されています。

この記事では、CDI (Contexts and Dependency Injection) for the Java EE platform プログラミング・モデルを ZK フレームワークとの関連で学びます。この記事でベースとするのは、ZK の威力を解説している記事、「ZK を使ったリッチ・インターネット・アプリケーション: オープンソースの Ajax フレームワーク」で使用したサンプル・アプリケーションです。顧客を詳細に管理できるこの実際のサンプル・アプリケーションを、この記事では ZK と CDI を使用して拡張します。

この記事のアプリケーションのソース・コードは「ダウンロード」セクションからダウンロードすることができます。

JSR-299 と CDI

JSR (Java Specification Request) 299、つまり CDI は、依存性注入とコンテキストによるライフサイクル管理に関する Java 標準です。この標準では、アプリケーション開発を大幅に簡易化し、簡潔にする一連のサービスを定義しています。これらのサービスでは以下のものを提供します。

  • イベンント通知メカニズムによるオブジェクト間の対話
  • タイプ・セーフな依存性注入
  • コンテキストに依存したステートフルなオブジェクトのためのライフサイクル・メソッド
  • インターセプターをオブジェクトにバインドする「デコレーター」というインターセプター
  • 移植可能な拡張機能を開発するためのサービス・プロバイダー・インターフェース (SPI)

CDI は疎結合と強い型付けに重点を置いていることから、実装やスレッド化モデル、あるいはライフサイクルなどの特定の側面を Bean が認識する必要はありません。これらの側面を個々のデプロイメントに応じてさまざまに変化させても、クライアントに影響することは一切ありません。疎結合により、コードを維持するのも、拡張するのも簡単になります。

ZK と CDI

ZK フレームワークで提供している ZK CDI は、CDI をシームレスに統合し、CDI サービスを ZK フレームワーク内で公開します。ZK CDI を使用すれば、エンタープライズ開発者は CDI 駆動のアプリケーションを ZK が提供する強力な Ajax フロントエンドに統合することができます。CDI と ZK を組み合わせて使用することにより、Java の EE Web 層と Java EE の間にある溝を何の苦労もなく埋めることができます。

ZK CDI エクステンションは、ZK プログラミング・モデル内で CDI の機能をシームレスに使用することを可能にします。ZK CDI エクステンションには組み込み CDI 機能の他にも、開発を容易にする以下の機能が用意されています。

カスタム変数リゾルバー/EL リゾルバー
<zscript /> 内に含まれる CDI 管理 Bean、EL 式 ( ${...})、および ZK アノテーション付きデータ・バインディング式 (@{…}) を、その EL 名によって解決します。
ZK カスタム・スコープ
組み込み CDI スコープ (Session、Request、Application、Conversation) に加え、この拡張はさらに 5 つの ZK スコープ (Desktop、Page、Execution、IdSpace、Component) を提供しています。
管理対象 Bean としての ZK コンポーネント
ZK コンポーネント (ZK コンポーザーなど) を管理対象 Bean に注入することができます。
ZK カスタム・アノテーションと CDI 提供のイベント通知モデルを用いた UI イベント・ハンドラー
任意のメソッドに ZK カスタム・アノテーションを付けて、イベント・ハンドラー・メソッドにすることができます。

ZK CDI エクステンションのベースとなっているのは、CDI を定義している JSR-299 仕様のリファレンス実装、Weld です (詳細については「参考文献」を参照)。


CDI と併せて ZK を使用する方法

このセクションでは、単純な例を用いて ZUL ファイルから CDI 管理対象 Bean にアクセスする方法を説明します (ZK ユーザー・インターフェース・ファイルのファイル拡張子は、.zul です)。CDI のコンテキストで言う管理対象 Bean (あるいは、単純な Bean) とは、他のコンポーネントへの注入、コンテキストとの関連付け、あるいは EL 式によるアクセスが可能な Java EE コンポーネントのことです。

リスト 1 に記載する単純な HelloWorld の例に、ZUL ファイル内の EL 式から管理対象 Bean にアクセスする方法を示します。まず、text という名前の 1 つの String フィールドと getText() というゲッター・メソッドを含めた HelloWorld クラスを定義します。CDI 仕様によると、no-arg デフォルト・コンストラクターを使用することで、このクラスは管理対象 Bean となります。Unified EL 式を使用して HelloWorld 管理対象 Bean を参照する場合には、この管理対象 Bean に @javax.inject.Named 修飾子のアノテーションを付ける必要があります。管理対象 Bean には、@Named 修飾子の値メンバーを指定することによって、任意の EL 名を付けることができます。値メンバーが指定されていない場合、EL 名には、Bean クラス名の先頭文字が小文字に変換された非装飾クラス名がデフォルトで設定されます。したがって、以下の例の HelloWorld は、helloWorld という EL 名にデフォルト設定されます。

リスト 1. 管理対象 Bean へのアクセス
@Named
@SessionScoped
public class HelloWorld implements Serializable {

	private String text = "HelloWorld";
	
	public String getText() {
		return text;
	}
}

リスト 2 に記載する ZUL コードは、HelloWorld Bean に、そのデフォルト設定の EL 名である helloWorld を使ってアクセスします。

リスト 2. デフォルトの EL 名
<?variable-resolver class="org.zkoss.zkplus.cdi.DelegatingVariableResolver"?>
<window title="ZK + CDI: Hello World" width="300px" border="normal">
    My CDI-injected bean says: ${helloWorld.text}
</window>

ZK には <?variable-resolver ?> という変数リゾルバー・ディレクティブがあります。このディレクティブを使用して、ZK EL エバリュエーター (${...})、ZK アノテーション付きデータ・バインダー (@{...})、そして <zscript> インタープリターが不明な変数を解決するために使用するリゾルバー・クラスを指定することができます。

DelegatingVariableResolver は、ZK CDI エクステンションがその機能の 1 つとして提供するカスタム EL リゾルバーです。このリゾルバーを使用することにより、Unified EL 式の中に含まれる EL 名によって HelloWorld 管理対象 Bean を解決することができます。

リスト 2 の index.zul ファイルでは、単純なウィンドウ・コンポーネントを定義し、${helloWorld.text} EL 式を使用して HelloWorld クラスのテキスト・フィールドを表示するようになっています。ZUL レンダリング・フェーズで ZULパーサーがこの EL 式を検出すると、<?variable-resolver ?> に指定された DelegatingVariableResolver を使って helloWorld Bean インスタンスを解決します。HelloWorld はコンテナー管理対象 Bean となっているので、コンテナーが HelloWorld Bean インスタンスを提供することになります。そしてこのコンテナーから返された Bean インスタンスを使用して、${helloWorld.text} が評価されます。getText() メソッドの評価により、図 1 に示された「Hello World」というストリングが返されます。

図 1. HelloWorld
「My CDI-injected bean says HelloWorld」というメッセージが表示された画面

ZK と CDI を使用した実際のアプリケーションの実装

このセクションでは実際のアプリケーションを実装する手順を通して、その他の CDI の機能を探っていきます。ここで実装するアプリケーションのベースは、「ZK を使ったリッチ・インターネット・アプリケーション」の記事で例に用いた顧客管理アプリケーションです。この顧客管理アプリケーションでは、新しい顧客を追加したり、顧客データを編集したり、データベースから顧客エントリーの論理削除を行ったりするなど、ユーザーがさまざまな操作を実行できるようになっています。図 2 に、顧客管理アプリケーションのメイン・ユーザー・インターフェース画面を示します。

図 2. 顧客管理アプリケーションのダッシュボード
ID、顧客の名前、日付、そして顧客のエントリーが削除済みであるかどうかが示された画面

上記の画面には、アプリケーションに登録済みの顧客のリストが表示されています。このリストはグリッドとして表示されており、Id (ID)、Name (顧客の名前)、Active Date (最後にアクションがあった日付)、そして Deleted? (削除) フラグの各列があります。グリッド内のデータは、列名の近くにあるボタンをクリックすることで、(昇順または降順で) ソートすることができます。ソートを行える列は、Id (int)、Name (String)、Active Date (Date) です。このアプリケーションではページネーション機能も有効になっていて、画面の下を見るとわかるように、ページには一度に 5 件のレコードが表示されるようになっています。ユーザーは、次のページを表示したり、または指定したページへ直接ジャンプしたりすることができます。

図 3 に、顧客管理アプリケーションの最上部のメニュー・バーを示します。

図 3. 最上部のメニュー・バー
新規顧客を登録するためのメニュー項目が示されたドロップダウン・メニュー

このメニュー・バーは、ZK の menubar ウィジェットを使って実装されています。このバーには、新規顧客を登録するための選択肢、そしてアプリケーションを終了するための選択肢があります。


開発環境のセットアップ

実装の詳細を掘り下げる前に、顧客を管理するサンプル・アプリケーションの作成について、いくつかの詳細を復習しておきます。ここでは Eclipse IDE を使用してアプリケーションを作成しますが、この方法はどの IDE でも有効なはずです。

基本的な考え方としては、動的 Web アプリケーションのプロジェクトを作成し、そのプロジェクトがアプリケーション・サーバー・ランタイムを指すようにします。この例でのランタイムは、Apache Tomcat ランタイムです。新しいプロジェクトとランタイムをセットアップしたら、ディレクトリー構造を図 4 のようにします。

図 4. ディレクトリー構造
zkManageCustomers のディレクトリー構造

顧客管理アプリケーションのディレクトリー構造は、図 4 のディレクトリー構造と同じパターンに従います。

このアプリケーションの中心となるファイル群は、WebContent フォルダーに置かれます。このフォルダーには、以下のサブディレクトリーが配置されています。

  • META-INF: MySQL データベースに接続するための、データベース・クレデンシャル情報が含まれています。
  • WEB-INF: ここにはアプリケーションを実行するために必要な ZK JAR ファイルが含まれるライブラリー・フォルダーがあります。また、以下の 3 つの構成ファイルも格納されています。
    • beans.xml: このアプリケーションに含まれる、注入に使用可能な Bean クラスを指定するファイルです。
    • web.xml: データ・ソース、ZK サーブレット、および Weld 構成を記述するファイルです。
    • zk.xml: このファイルには、ZK 固有の構成が含まれます。

上記の他、関連する ZUL および HTML ファイルはすべて、WebContent フォルダーに格納されます。これらのファイルはアプリケーションのビュー部分として機能し、動的コンテンツと静的コンテンツを Web アプリケーションに提供します。

zkManageCustomer.zip という名前のサンプル・ファイル (「ダウンロード」を参照) には、アプリケーションの zip 版が含まれています。またこのサンプル・ファイルには、Eclipse に必要なメタデータ・ファイルも含まれているため、そのまま直接 IDE にインポートすることができます。

Eclipse の「J2EE (Java 2 Platform, Enterprise Edition)」パースペクティブには「Server (サーバー)」タブがあり、このタブを右クリックすると、新規サーバーを作成するための選択肢が表示されます。この選択肢から新規に作成したサーバーを使用して、Eclipse IDE からアプリケーション・サーバーを管理することができます。

新しいサーバーを構成した後は、新たに作成するリソースはそのサーバー上で構成する必要があります。このようにサーバーを構成することによって、開発の過程で作成されるリソースがデプロイされます。

MySQL を構成する

このサンプル・プログラムは、Tomcat と MySQL を使って動作するように構成されていますが、CDI に対応した別のアプリケーション・サーバーでも問題なく実行できるはずです。このサンプルでは JDBC を使用するため、JDBC をサポートする SQL データベース (DB2 Express-C など) であれば、どのデータベースを使ったとしても接続コードを少し変更するだけで動作するはずです。

Tomcat を MySQL データベースに接続するためには、リソース参照を定義する必要があります。このリソース参照要素によって、リソース・マネージャー接続ファクトリーの参照名を指定します (リスト 3 を参照)。この例でリソース参照に該当するのは、jdbc/mysql という名前のデータベース接続で、その型は javax.sql.DataSource です。

リスト 3. リソース参照の定義
<resource-ref>
      <description>DB Connection</description>
      <res-ref-name>jdbc/mysql</res-ref-name>
      <res-type>javax.sql.DataSource</res-type>
      <res-auth>Container</res-auth>
</resource-ref>

また、WebContent/META-INF フォルダー配下の context.xml ファイルの中で接続リソースを定義する必要もあります。このファイルには、ドライバー名、jndi 名、ユーザー名、パスワード、データ型、URL などのプロパティーが含まれています。

リスト 4. 接続リソースの定義
<Resource driverClassName="com.mysql.jdbc.Driver" 
	maxActive="4" maxIdle="2" maxWait="5000" auth="Container" 
	name="jdbc/mysql" password="as1008" type="javax.sql.DataSource" 
	url="jdbc:mysql://localhost:3306/customer" username="root"/>

この顧客データベースには、リスト 5 のスクリプトを実行することで作成されるテーブルが 1 つあります。

リスト 5. テーブルの作成
use customer;
CREATE TABLE 'customer' (
	  'ID' int(11) NOT NULL AUTO_INCREMENT,
	  'name' varchar(255) DEFAULT NULL,
	  'date' date DEFAULT NULL,
	  'deleted' tinyint(1) DEFAULT '0',
	  PRIMARY KEY ('ID')
	);

ZK CDI エクステンションのサポートを有効にする

ZK CDI エクステンションを利用するには、zkcdi.jar ファイル (ZK CDI エクステンションのバイナリー・ディストリビューション) を Web アプリケーション・プロジェクトの WEB-INF/lib フォルダーにコピーします。この zkcdi.jar ファイルを、Web アプリケーション・ライブラリーとして使用してください。

Tomcat の Weld サポートを有効にする

Weld サポートを Tomcat に対して有効にする手順は以下のとおりです。

  1. Web ルートにある WEB-INF/web.xml ファイルに、以下のように Weld サーブレット・リスナーを指定します (Weld サーブレット・リスナーは、Weld をブートし、リクエストとの対話を制御するために使用されます)。
    <listener>
       <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
    </listener>
    <listener>
       <listener-class>org.jboss.weld.el.WeldELContextListener</listener-class>
    </listener>
  2. Tomcat の JNDI は読み取り専用なので、Weld は自動的に BeanManager 拡張 SPI をバインドすることができません。BeanManager を JNDI にバインドするには、Web ルートにある META-INF/context.xml に以下の内容を入力し、その下のリストに記載されている方法に従って、デプロイメントで使用できるようにしてください。
    <Resource name="BeanManager" auth="Container"
    		type="javax.enterprise.inject.spi.BeanManager" 
    		factory="org.jboss.weld.resources.ManagerObjectFactory" />
          <resource-env-ref>
          <description>Object factory for the CDI Bean Manager</description>
          <resource-env-ref-name>BeanManager</resource-env-ref-name>
          <resource-env-ref-type>javax.enterprise.inject.spi.BeanManager
         </resource-env-ref-type>
    </resource-env-ref>
  3. CDI による依存性注入を可能にするために、WEB-INF フォルダーに空の beans.xml ファイルを追加します。

他のアプリケーション・サーバーや環境で Weld をセットアップする手順も同様です。詳しくは、「参考文献」を参照してください。


アプリケーションの拡張

この顧客管理アプリケーションには以下の機能があります。

  • ユーザーが操作できるダッシュボード (すべての顧客のリストが表示されます)
  • 新しい顧客の追加
  • 既存の顧客の編集
  • 顧客の削除 (論理削除)

図 2 に示したダッシュボードには、登録されたすべての顧客のリストが表示されています。このリストは、ID または名前でソートすることができます。index.zul ファイルには、さまざまな属性 (borderlayoutmenubarmenumenupopup など) があり、これらの属性によってアプリケーションのルック・アンド・フィールが定義されます。

リスト 6. ビューの定義
<?page id="manageCust" title="Manage Customers" cacheable="false" 
	language="xul/html" zscriptLanguage="Java" contentType="text/html;charset=UTF-8"?>
	<?variable-resolver class="org.zkoss.zkplus.cdi.DelegatingVariableResolver"?>
<zk>
  <window id="win" border="normal" width="810px" minheight="300"
   apply="${manageCustomer}">
      <caption label="Manage Customers"/>
         <borderlayout height="30px">
	     <north border="none">
	        <menubar id="menubar" width="800px">
		   <menu label="Manage Customers">
		      <menupopup>
		         <menuitem id="add" label="Register New Customer">
			  </menuitem>
			  <menuseparator />
			  <menuitem id="exit" label="Exit" onClick="win.detach()" />
		       </menupopup>
		    </menu>
		 </menubar>
	      </north>
	  </borderlayout>
      <listbox id="customerList" mold="paging" pageSize="5" multiple="true" width="800px">
	   <listhead sizable="true">
	      <listheader label="Id" sort="auto(id)"/>
	      <listheader label="Name" sort="auto(name)"/>
	      <listheader label="Active Date" sort="auto(date)"/>
	      <listheader label="Deleted?" />
	   </listhead>
	</listbox>
</window>
</zk>

以前の顧客管理アプリケーションの実装とは異なり、この例では ZK の MVC 手法を使って、ビューをコントローラーから分離しています。したがって、index.zul ファイルにはアプリケーションのビューの部分しか含まれていません。コントローラーのコードはこれとは別に、コンポーザー・クラス ManageCustomer に作成します。こうすることによって、ZK CDI の機能を利用できるようになります。

ZK の MVC 手法では、apply 属性を使用してコントローラーを特定のコンポーネントに適用することができます。上記の例では、apply 属性を使用して ManageCustomer をダッシュボードのメイン・ウィンドウ・コンポーネントに適用しています。この例には、DelegatingVariableResolver の変数リゾルバー・ディレクティブの使い方と、EL 式を使用して、コンテナーが提供する ManageCustomer 管理対象 Bean インスタンスを適用する方法も示されています。

ダッシュボード・ページの主要なコンポーネントには以下の 2 つがあります。

  • 最上部のメニュー。このメニューには、「Register New Customer (新規顧客の登録)」と「Exit (終了)」という 2 つのサブメニュー項目があります。

    ZK のメニュー・バー・コンポーネントは、menubar です。このコンポーネントには ID がそれぞれ addexit に設定された 2 つの子 ZK menuitem コンポーネントがあります。

  • データベース内に現在保管されている顧客を掲載する表。この例での表は、ID が customerList に設定された ZK の listbox コンポーネントです。表の列名は、customerList リスト・ボックスの子コンポーネント listheader で定義されます。

ここまではダッシュボードの UI を定義するコンポーネントについて詳しく見てきましたが、データについてはどうでしょうか。customerList リスト・ボックスが入力されるタイミングについては、リスト 7 に記載する ManageCustomer コントローラー・クラスを見てください。

リスト 7. ManageCustomer
@Named
@SessionScoped
public class ManageCustomer extends GenericComposer implements Serializable {	
	@Inject @SessionScoped CustomerService custSvc;
	@Inject @ComponentId("customerList") private transient Listbox customerList;
	/**
	 * Set up list of all customers on the dashboard
	 * @param component
	 */
	public void doAfterCompose(Component component) throws Exception {
    	   super.doAfterCompose(component);
	   List myList = custSvc.getAllCustomers();
	   ListModelList lm = new ListModelList(myList);
	   customerList.setModel(lm);
	   customerList.setItemRenderer(new CustomersListboxRenderer());
	}
...

まず初めに ManageCustomer Bean クラスに @Named 修飾子でマークを付けて、Unified EL 式でアクセスできるようにします。EL 式によってアクセスすることは、index.zul ファイル内でウィンドウ・コンポーネントの apply 属性値が “${manageCustomer}” に指定されていることによって示されています。

次のステップでは、CDI のタイプ・セーフな依存性注入機能を使用して、CustomerService Bean を ManageCustomer コントローラーの中に注入します。CustomerService Bean は、データベースにアクセスして顧客情報を追加、更新、削除、取得するためのメソッドを実装するユーティリティー・クラスです。このユーティリティー・クラスを注入するには、ManageCustomer クラス内に CustomerService フィールドを指定し、@javax.inject.Inject のアノテーションを付けます。CDI 仕様によると、コンテナーによって ManageCustomer Bean インスタンスがインスタンス化されると、その中には常に CustomerService Bean インスタンスが自動的に注入されることになります。

この時点で次に必要となるのは、customerList リスト・ボックスにアクセスしてすべての顧客データを入力することです。この例では、ZK CDI エクステンションが提供する GenericComposer ユーティリティー・クラスを使用しています。ManageCustomer クラスを org.zkoss.cdi.util.GenericComposer で拡張し、ウィンドウ・コンポーネントの子コンポーネントが ManageCustomer コントローラー・クラスに自動的に注入されるようにしてください。ZK コンポーネントを自動的に注入するには、index.zul ファイルに指定されているコンポーネント ID と併せて、@org.zkoss.cdi.inject.ComponentId 修飾子を使わなければなりません。さらに ZK CDI エクステンション仕様に従って、ComponentId 修飾子のメンバー値をフィールド名と一致させる必要もあります。例えば、customerList リスト・ボックスを ManageCustomer に注入する場合には以下のように指定します。

@Inject @ComponentId("customerList") private transient Listbox customerList;

いよいよ、このリスト・ボックスにデータを入力します。データ入力は、クライアント上でブラウザーによってページがレンダリングされる前に行わなければなりません。GenericComposer の型は Composer なので、doAfterCompose() メソッドが必要になります。このメソッドは、すべてのコンポーネントが組み立てられた後に呼び出されるコールバック・メソッドです。このコールバック・メソッドによって、すべての ZUL コンポーネントが構成されてからレンダリングが行われるまでの間に顧客リスト・ボックスにデータを入力することが可能になります。ただし、GenericComposerdoAfterCompose() を呼び出してからでないと、オーバーライドされた doAfterCompose() メソッドでの処理は行えません。

新しい顧客を登録する

「Register New Customer (新規顧客の追加)」(顧客管理ダッシュボードのメニュー・バーにある menuitem “add”) がクリックされるとどうなるのでしょうか。このメニュー項目がクリックされた場合には、「Enter Customer Data (顧客データの入力)」ウィンドウを表示し、このウィンドウに入力されたデータが新規顧客レコードとしてデータベースに保管されるようにしなければなりません。そのためにはまず、ManageCustomer コントローラーに menuitem “add” の onClick イベントに対するイベント処理メソッドを用意する必要があります。イベント処理メソッドは、ZK CDI エクステンションが定義する @org.zkoss.cdi.event.Events 修飾子と CDI が定義するイベント通知モデルを使用すれば簡単に定義することができます。このアプリケーションの場合には、リスト 8 に記載する単純な registerNewCustomer() メソッドを定義します。

リスト 8. registerNewCustomer() メソッドの定義
public void registerNewCustomer(@Observes @Events("add.onClick")  
                MouseEvent evt) throws Exception {
   Window win1 = (Window)Executions.createComponents("addCustomer.zul", null, null);
   win1.doModal();
   win1.setTitle("Enter Customer Data");
   win1.setClosable(true);
   win1.setMaximizable(true);
}

上記の例では、ZK イベント型の 1 つである MouseEvent 型のメソッド・パラメーターに @javax.enterprise.event.Observes というアノテーションを付けています。このアノテーションを付けることにより、registerNewCustomer() メソッドはオブザーバー・メソッド (MouseEvent 型を監視するメソッド) になり、CDI が MouseEvent 型のイベントを発行するたびに、このメソッドに通知されることになります。MouseEvent 型のイベントにはさまざまな種類があるはずなので、それぞれの種類を区別できなければなりません。そのための手段となるのが、@Events 修飾子です。@Events 修飾子にメンバー値を指定することによって、このメソッドに通知する必要のあるイベントの種類を指定することができます。@Events のメンバー値は、コンポーネント ID の後にイベント名を続けるという形で指定します。例えば、menuitem “add” の onClick イベントの場合には、@Events 修飾子に「add.OnClick」という値を指定します (リスト 7 を参照)。

「Register New Customer (新規顧客の登録)」メニュー項目がクリックされた場合のイベント・シーケンスを説明すると、まず初めに MouseEvent 型の ZK イベントがサーバーに送信されます。ZK フレームワークがこのイベントを受け取ると、ZK CDI エクステンションは@Events 修飾子と、MouseEvent 型でのメンバー値を設定した CDI イベントを発行/起動します。そして CDI は、MouseEvent 型を監視中のメソッドに、正確な @Events 修飾子とメンバー値を通知します。

リスト 9. 顧客の追加
<?page title="Add Customer" contentType="text/html;charset=UTF-8"?>
<?variable-resolver class="org.zkoss.zkplus.cdi.DelegatingVariableResolver"?>
<zk>
   <window id="addCustomerWin" title="Register New Customer" border="normal"
       apply="${addCustomer}">
      <grid fixedLayout="true" width="450px">
         <rows>
	    <row>
	       <label value="Customer Name" />
		<textbox id="customerName" constraint="no empty" />
	    </row>
	    <row>
	        <label value="Date" />
		   <datebox id="date" constraint="no empty"/>
	      </row>
	      <row>
		 <button id="saveBtn" label="Save" />
		 <button id="cancelBtn" label="Cancel" onClick="addCustomerWin.detach()"/>
	       </row>
	     </rows>
       </grid>
   </window>
</zk>

registerNewCustomer() メソッドが呼び出されると、このメソッドは addCustomer.zul を使用して新しいウィンドウ・コンポーネントを作成し、それをモーダル・ダイアログ・ウィンドウにします。図 5 に一例を示します。

図 5. 新規顧客の登録
「Customer Name (顧客名)」および「Date (日付)」を入力する各フィールドと、「Save (保存)」ボタンと「Cancel (取り消し)」ボタンが表示された画面

index.zul ページと同じく、この例では <?variable-resolver ?> ディレクティブと DelegatingVariableResolver を使用して、addCustomerWin ウィンドウ・コンポーネントに、AddCustomer 管理対象 Bean をコントローラーとして適用しています。顧客登録ダイアログは、グリッドとして実装されています。このグリッドでは、「Customer Name (顧客名)」と「Date (日付)」フィールドに値を入力しなければなりません。AddCustomer の実装は、ManageCustomer クラスの場合と同じように、@Inject 修飾子によって CustomerService Bean が注入され、さらに @Inject 修飾子と @ComponentId 修飾子によって addCustomerWin の子コンポーネントが注入されます (リスト 10 を参照)。

リスト 10. 顧客の追加
@Named
@SessionScoped
public class AddCustomer extends GenericComposer implements Serializable {
   @Inject @SessionScoped CustomerService custSvc;
   @Inject @ComponentId("customerName") private transient Textbox customerName;
   @Inject @ComponentId("date") private transient Datebox date;
   @Inject @ComponentId("saveBtn") private transient Button saveBtn;
}

この例では、ManageCustomer クラスの registerNewCustomer() メソッドと同じように saveNewCustomerDetails() オブザーバー・メソッドを実装します (リスト 11 を参照)。

リスト 11. 新規顧客データの詳細情報の保存
public void saveNewCustomerDetails(
   @Observes @Events("saveBtn.onClick") MouseEvent evt, @New Customer newCustomer)
      throws Exception {
   newCustomer.setName(customerName.getValue());
   java.util.Date utilDate = date.getValue();
   java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());
   newCustomer.setDate(sqlDate);
   custSvc.addCustomer(newCustomer);
   Executions.getCurrent().sendRedirect("index.zul");
   evt.getTarget().getParent().detach();
}

「Save (保存)」ボタン (saveBtn) がクリックされるたびに、対応する onClick MouseEvent が saveNewCustomerDetails() メソッドに通知されます。このメソッドには、Customer 型のもう 1 つのパラメーターがあります。このパラメーターには @New 修飾子が付けられていますが、その理由は CDI 仕様では、このメソッドが観測されると常に Customer Bean の新規インスタンスがパラメーターに自動的に注入されることになっているためです。

顧客の編集/(論理) 削除

ユーザーが顧客管理ダッシュボードで任意の顧客の行を選択すると、「Edit Customer (顧客の編集)」画面が表示されます (図 6 を参照)。

図 6. 顧客の編集
「Customer Name (顧客名)」、「Date (日付)」、そしてチェック・マークが付けられた「Delete? (削除)」ボックスが表示された画面

registerNewCustomer() メソッドと同様に、この例ではオブザーバー・メソッドを定義しています。この editCustomer() メソッドでは、ZK の SelectEvent@Observes アノテーションが付けられており、リスト・ボックス内のリスト項目が選択されると、この SelectEvent が送信されます。

リスト 12. 顧客の編集
public void editCustomer(
   @Observes @Events("customerList.onSelect") SelectEvent evt) throws Exception {
      Listitem selectedCustomer = customerList.getSelectedItem();
      String custId = ((Listcell) (selectedCustomer.getChildren().get(0))).getLabel();
      Map<String, String> args = new HashMap<String, String>();
      args.put("custId", custId);
      Window win2 = (Window) Executions.createComponents("editCustomer.zul", null, args);
      win2.doModal();
      win2.setTitle("Enter Customer Data");
      win2.setClosable(true);
      win2.setMaximizable(true);
}

editCustomer.zul の editCustomerWin ウィンドウ・コンポーネントを対象とした EditCustomer コントローラー・クラスの実装は AddCustomer とよく似ているので、重複を避けるため、この記事では説明しません。

このアプリケーションのコード、EditCustomer クラスのソース、および editCustomer.zul ファイルはダウンロードすることができます。


まとめ

My developerWorks の Web Development グループに参加してください

My developerWorks の Web Development グループで、他の開発者と Web 開発について議論し、リソースを共有してください。

まだ My developerWorks のメンバーになっていなければ、今すぐ登録してください!

この記事では、Java コードで作成されたオープンソースの Ajax フレームワーク ZK と、JSR-299 Contexts and Dependency Injection について説明しました。ZK CDI エクステンションに用意された機能によって、ZK プログラミング・モデル内で CDI を使用できるようになります。この記事ではこれらの機能を、Apache Tomcat 上で実行し、MySQL データベースに接続する単純な実際のアプリケーションを使用して説明しました。

ZK フレームワークは、豊富なコンポーネント、マークアップ言語、強力な開発ツール、そして優れたドキュメントが揃った、オープンソースのイベント駆動型 Ajax フレームワークです。Java EE プラットフォーム 6 の JSR-299 によって定義された CDI もまた、タイプ・セーフな依存性注入、イベント通知モデル、そして移植可能な拡張を開発するための SPI をはじめ、強力な一連の機能を提供します。ZK プログラミング・モデルと CDI を統合する ZK CDI エクステンションによって、シームレスな Java EE 6 エンタープライズ・アプリケーション開発が可能になります。


ダウンロード

内容ファイル名サイズ
Sample code for this articlezkManageCustomers.zip71 KB

参考文献

学ぶために

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Java technology
ArticleID=497371
ArticleTitle=ZK での CDI プログラミング・モデルを探る
publish-date=05252010