第6回 EJBにおけるコンテナとコンポーネント
――EJBの定義と、2つのインターフェイスの役割――
丸山不二夫
稚内北星学園大学学長
(http://www.wakhok.ac.jp/)
2001/9/13
今回から、J2EEの本体ともいえるEJB(Enterprise Java Beans)の話を始めたいと思います。EJBには、セッション・ビーン(Session Bean)とエンティティ・ビーン(Entity Bean)、メッセージ・ドリブン・ビーン(Message-driven Bean)の3種類があります。途中で追加されたMessage-driven Beanを除いて、Session BeanとEntity Beanでは、1つのビーン(Bean)を定義するのに3つのクラス・ファイルを必要とします。
EJBに初めて触れたとき「どうして3つも定義ファイルが必要なの?」と、感じた人は多いと思います。今回は、この、1つのBeanの定義に、Bean実装クラスのほかに、HomeとRemoteと呼ばれる2つのインターフェイスが、なぜJ2EEでは必要とされるのかを説明します。
Session Beanとは |
Session Beanには、ユーザーがサーバと接続している間(セッション)に存続するものという意味があります。そしてその内容は、1人のユーザーがセッション中にサーバに対して依頼する何らかの処理をサーバ側のコンポーネントにまとめたものと考えてよいでしょう。イメージ的には、サブルーチンの呼び出しに近いもので、その機能のネットワーク上での拡大版である、RMI(Remote Method Invocation)の働きをコンポーネント化したものと考えることができます。
Session Beanは、サーバとの対話的なセッションにおいて、セッション中保持されるべき状態を持つか否かに応じて、statefullとstatelessの2つの種類に分かれます。例えば、ECサイトで利用される「買い物カゴ」は、何がカゴの中に入っているかという状態の情報が意味を持ちますので、statefullなSession Beanになります。一方、データベースにアクセスして、J2EEに関する情報を集めよという命令に対応するBeanは、セッション中の途中の状態は意味を持ちませんので、stateless Session Beanとして実現されます。
Hello EJBサンプルを見てみる |
最初に、J2EEサーバから“Hello”のメッセージを受け取る簡単なSession Beanのサンプルを紹介したいと思います。この場合、セッション途中のBeanの状態が保持される必要はありませんので、statelessなSession Beanを使います。
まず、サーバ側ですが、次の3つのファイルから“Hello”のメッセージを送り出す1つのビーンが構成されます。3つのクラスの中を調べてみると“Hello World!”の文字列を返すという、実際の仕事を実行しているのはEJBクラスの中のsayHelloメソッドの定義部分だけであることが分かります。
それでは、そのほかの部分は、この簡単なサンプルにとっては無駄にも思えるのですが本当に必要なのでしょうか? 残念ながら必要です。その理由は、おいおい明らかになると思います。
Remoteインターフェース public interface Hello extends javax.ejb.EJBObject
{ |
Homeインターフェース
public interface HelloHome extends javax.ejb.EJBHome
{ |
EJBクラス
public class HelloEJB implements javax.ejb.SessionBean
{ |
サーバ側の設定だけではこのサンプルは動きません。クライアント側に、以下のようなコードが必要になります。「簡単」といっても、プリント文で“Hello World!”をコンソールに表示するのとはだいぶ複雑さが違います。
import javax.naming.Context; |
deployとクライアントの実行 |
サーバ側とクライアント側のコードがそろえばそれでいいわけではありません。これらのファイルをコンパイルした後で、さらに、deploytoolを使ってdeployし、クライアント用のJARファイルを生成しなければなりません。deployするためには、もちろんJ2EEサーバを立ち上げる必要があります。今回は、deployの手順は細かく説明できませんので、基本的な部分を図示しておきます。
図1 ここでは、Stateless Session Beanが選ばれていることと、3つのクラス・ファイルが指定されていることに注意してください |
図2 BeanのJNDI名を指定します。これは、クライアントでのlookupの引数に対応しています |
図3 Application Client Jarファイルを生成させます |
deployが終わると、HelloClient.jarというファイルが作られているはずです。このファイルに対して、次のように、%J2EE_HOME%\bin以下にあるrunclient.batコマンドを使うとクライアント・プログラムが実行できます。
runclient -client HelloClient.jar
-name HelloClient |
このとき、認証用のウィンドウがポップアップして、ユーザー名とパスワードの入力を求められるはずです。特別な設定をしていなければ、双方にj2eeと打ち込めば、プログラムの実行が始まるはずです。このように、ユーザーの認証機能が自動的に組み込めるのもJ2EEの強力な機能の1つです。
3つの定義ファイル |
今回は、前節で紹介したサンプルでSession Beanを定義しているサーバ側の3つのファイルについて少し詳しく見てみたいと思います。
Remoteインターフェイス |
まず、最初のRemoteインターフェイスについてです。ここでは、まず、次のようなEJBObjectの定義を確認しておきましょう。EJBObjectインターフェイスがRemoteインターフェイスと呼ばれるのは、この定義によっています。
public interface EJBObject extends Remote { |
EJBのRemoteインターフェイスで重要なことは、クライアントがリモートで呼び出すRemoteExceptionを投げるリモート・メソッドとして、sayHelloメソッドが置かれていることです。このように、クライアントがJ2EEサーバから呼び出すべきメソッドは、すべてリモート・メソッドとして、このRemoteインターフェイスに記述されている必要があります。ビジネス・アプリケーションを主要なターゲットとしているJ2EEでは、こうしたメソッドを一般的に、「ビジネス・メソッド」とか「ビジネス・ロジック」と呼ぶことがあります。
Homeインターフェイス |
EJBのHomeインターフェイスも、定義的には先のEJBObjectと同じように次のような形をしています。このインターフェイスは、J2EEではHomeインターフェイスと呼ばれて、Remoteインターフェイスと呼ばれることはないのですが、本来は、EJBObjectと同じくRemoteインターフェイスであることには注意が必要です。それは、この中で定義されている、createメソッドがCreateExceptionとともにRemoteExceptionを返すことにも、はっきりと現れています。このように、HomeインターフェイスがRemoteインターフェイスでもあるということは、後で見るように、3つの定義ファイルの関係を考えるうえで大きな意味を持っています。
public interface EJBHome extends Remote { |
Homeインターフェイスで重要なことは、createというメソッドの名前とこのメソッドが返すオブジェクトの型です。先のRemoteインターフェイスで出てくるメソッドは、あるビジネス・アプリケーションのビジネス・ロジックを表現するメソッドたちですから、アプリケーションに応じて変わります。ところが、Homeインターフェイスのcreateメソッドという名前はアプリケーションには依存しません。しかし、このcreateメソッドの返す型は、この例の場合にはHelloですから、ちょうど先に見たRemoteインターフェイスの型に等しくなっています。このように、createが返す型はアプリケーションによって変わり、そのときのRemoteインターフェイスの型を取ります。
「Homeインターフェイスのcreateメソッドは、Remoteインターフェイスの型を返す」
これが、2つのインターフェイスの間の最も重要な関係になっています。同時に、この関係はクライアントの側から、J2EEのBeanのメソッドを呼び出すときにも、基本的な役割を果たします。
EJBクラス |
3番目のEJBクラスは、一見するとsayHelloメソッドが「実装」されていますので、Remoteインターフェイスを「実装」しているように思えるかもしれません。ところが、単純にそうではないのです。SessionBeanの定義は次のようになっています。
public interface SessionBean extends EnterpriseBean
{ |
これだけだと分からないのですが、EnterpriseBeanの定義を見ると次のようになっていて、SessionBeanが、SerializableではあるけれどもRemoteではないことが分かります。
public interface EnterpriseBean extends
Serializable { |
このことは、EJBクラスのメソッドたちが、基本的にはリモートではなくローカルなメソッドとして定義されていることを見ても分かります。EJBのメソッドの実装定義と思えたEJBクラスが、実は、そのままではネットワークを通じたリモート・メソッドの呼び出しの対象にはなりえないというのは奇妙なことです。このなぞは後で解くことにして、ここではRemoteインターフェイスで定義されたメソッドは、このEJBクラスの中で、ローカルなメソッドとして「実装」されるべきことを確認しておきましょう。
Remoteインターフェイスが必要な理由 |
なぜ、2つのインターフェイスが必要かという問いに答えようとして、Session Beanを定義する3つのクラス・ファイルを見てきましたが、むしろ疑問が増えたかもしれません。Remoteインターフェイスを必要とするのは、明らかに、ネットワークを通じて、サーバ内のコンポーネントでRMIを実行しようとしているからだと思われるのですが、よく見ていくと、EJBを定義する3つのクラス・ファイルだけで、そのことを説明するのは難しいことが分かります。3つのファイルは多すぎるように見えますが、むしろ、何かが欠けているのです。
では、RMI/IIOPを使った同じプログラムと |
ここでは、サーバから“Hello World!”のメッセージを受け取る、先のEJBのサンプルと同じプログラムをRMI/IIOPのプロトコルを使って作ってみて、それと先のサンプルとの比較をしてみたいと思います。ちなみに、J2EEもこのRMI/IIOPのプロトコルを使っています。
まず、RMIで呼び出されるべきリモート・メソッドを定義するRemoteインターフェイスですが、これは、ほとんど先のEJBのRemoteインターフェイスと同じです。
Hello Remoteインターフェイスpublic interface Hello extends java.rmi.Remote
{ |
サーバ側のプログラムは、次のようなものです。このクラスが、第1にPortableRemoteObjectを拡大したものであること、第2にRemoteインターフェイスであるHelloインターフェイスを実装していること、すなわち、sayHelloメソッドをリモート・メソッドとして実装していることはすぐに分かると思います。もう1つ、メイン・メソッドが、HelloServerという名前で、JNDIサーバに自分自身を登録していることも分かります。
HelloServer import java.io.*; public class HelloServer extends PortableRemoteObject
implements Hello {
|
問題は、こうしたRemoteインターフェイスに対して、サーバ側で対応すべきコードがEJBのサンプルではほとんど見られないということです。
単純なRMIでのクライアント側のコードは、次のようになります。まず、JNDIからHelloServerという名前でオブジェクトを獲得して、そのオブジェクトをnarrowメソッドを使ってHello.classの型を合わせます。こうして、RemoteインターフェイスHelloを持つオブジェクトを獲得しています。次いで、そのオブジェクトで、sayHelloメソッドを呼び出しています。
こうしたRMIでのコードを、EJBでのクライアント側のコードと比較してみてください。JNDIを通じて獲得されるオブジェクトが、EJBのクライアントではHelloHomeです。これは先にも注意しましたが、EJBでは、Remoteインターフェイスと区別されてHomeインターフェイスと呼ばれているものが、RMIのRemoteインターフェイスでもあることを考えれば、不思議ではありません。むしろ、HelloHomeをRemoteインターフェイス、createメソッドをそのRemoteインターフェイスで定義されたリモート・メソッドと割り切って、EJBのクライアント側のコードを眺めれば、その前半部分は、RMIとまったく同じコードとしてきれいに解釈できるのです。
HelloClient
import javax.naming.*;
|
2つのRemoteインターフェイス |
「前半」というのは、EJBクライアントの次のような部分です。JNDIを通じて、サーバが提供する、RemoteインターフェイスとしてのHelloHomeインターフェイスを持つインスタンスhomeを獲得して、そのインスタンス上でリモート・メソッドcreateを呼び出していると解釈できます。ここでの主役は、RemoteインターフェイスであるHelloHomeインターフェイスです。
HelloHome home = (HelloHome) PortableRemoteObject.narrow( |
EJBクライアントの「後半」部分は次のような部分です。ここでは、主役が、HelloHomeインターフェイスから、EJBのRemoteインターフェイスであるHelloインターフェイスに交代しています。前半では、Remoteインターフェイスを持つインスタンスは、JNDIを通じて獲得されましたが、ここでは、createメソッドの呼び出しを通じて獲得されています。いったんこうしたインスタンスが獲得されれば、リモート・メソッドの呼び出しが可能となるわけです。
Hello hello = home.create(); |
ここでは、先に見た、次のような命題が、前半部分と後半部分の2つのRemoteインターフェイスを結び付けるカナメの役割を果たしていることを、あらためて確認しておきましょう。
「Homeインターフェイスのcreateメソッドは、Remoteインターフェイスの型を返す」
隠れているサーバ側のプログラムを想像する |
しかし、まだ釈然としないところがあります。理由ははっきりしています。RMIのサンプルの場合には、サーバ側とクライアント側の2つのプログラムが与えられ、双方がRemoteインターフェイスを共有していることがはっきり分かるのに対して、EJBのサンプルでは、2つのRemoteインターフェイスと、その2つのRemoteインターフェイスを両方利用しているクライアント・プログラムは与えられているものの、サーバ側のプログラムが明確でないのです。sayHelloメソッドを実装しているEJBクラスは、確かにサーバ側に属するのですが、残念ながら、Remoteインターフェイスとの対応を欠いています。これは、先のhello.sayHello()というシーケンスにおいて、helloがリモート・インスタンスであり、sayHelloがEJBクラスで実装されたメソッドであるなら、この組み合わせでは、メソッドの呼び出しは行えないということにほかなりません。これでは困ります。
先のEJBのクライアント側のプログラムが動くためには、何らかの補助的なクラスの助けが必要なのです。RMIでのリモート・メソッドの呼び出しがうまく動くためには、何よりも、EJBの2つのRemoteインターフェイスについて、対応するサーバ側のプログラムの存在が不可欠です。ここで、そうしたサーバ側のプログラムを想像してみましょう。RMIの流儀に忠実に考えていくと、次のような骨組みが浮かび上がってきます。
Homeインターフェイス HelloHomeに対応するサーバ・プログラムHelloHome_Server
public interface HelloHome extends javax.ejb.EJBHome
{
|
Remoteインターフェイス Helloに対応するサーバ・プログラム
Hello_Server
public interface Hello extends javax.ejb.EJBObject
{ |
逆に、サーバ側に2つのRemoteインターフェイスに対応するこうしたオブジェクトが存在するなら、EJBのクライアント側のプログラムの働きが、RMIの働きとしてちゃんと説明できることが分かります。
生成されたファイルを調べる |
実際に、J2EEはこのような処理を行っています。いくつかの点では手直しが必要ですが、基本的なシナリオはいま述べたとおりです。J2EEは、与えられた2つのRemoteインターフェイス(J2EEでは、RemoteインターフェイスとHomeインターフェイスといいます)とEJBクラスの定義から、新たに2つのRemoteインターフェイスに対応するサーバ側のクラスのJavaコードを生成し、それをコンパイルしてクラス・ファイルを生成し、それをVMにロードしてオブジェクトを作っています。
こうした新しいクラスの生成は、実は、アプリケーションのdeploy時に、自動的に行われています。deploy時に、コンパイル済みのクラス・ファイルしか与えていないはずなのに、Compiling wrappercode...とかCompiling RMI-IIOP code...といったメッセージが出ていたのは、そのせいです。deployには、いろんな顔があるのです。生成されたクラス・ファイルは、J2EEがインストールされたディレクトリrepository以下の次の場所に置かれています。
%J2EE_HOME%\repository\<ホスト名>\gnrtrTMP\
<Remoteインターフェイス名>
今回のHelloの例では、次のようなクラス・ファイルが作られていることが分かります。ネーミング・ルールは、先の「想像」とは違っていますが、対応の見当はつくと思います。
「3つも定義ファイルがあるのはなぜ?」という問いかけから始まったのですが、現実は、もっと複雑そうです。
HelloEJB_EJBObjectImpl.class <----
先の「想像」での |
確かに複雑ですが、次のように整理すれば、EJBのHomeとRemoteの2つのインターフェイスに対応した、クライアントとサーバをRMI/IIOPで結ぶ、2つの系列のクラス群であることが見て取れるはずです。
クライアント側Stub | RMI用 Tieクラス | サーバコンポーネント |
_HelloHome_Stub.class | _HelloHomeImpl_Tie.class | HelloHomeImpl.class |
_Hello_Stub.class | _HelloEJB_EJBObjectImpl_Tie.class | HelloEJB_EJBObjectImpl.class |
コンテナとコンポーネント |
ようやく、J2EEのサーバ側の「仕掛け」が見えてきました。先のHelloEJBの例で説明してみましょう。
J2EEは、サーバ内に、与えられたEJBのRemote、Home、EJBクラスの3つの定義ファイルを受け取って、それを処理するオブジェクトを作り出します。そのオブジェクトは、3つの定義ファイル内のメソッドに関する情報を、すべてピックアップします。このオブジェクトが、「コンテナ」と呼ばれているものです。J2EEは、deploy時に、Homeインターフェイスからそれに対応するサーバ側のオブジェクトを生成します。このオブジェクトは、コンテナと1対1で対応していてコンテナにセットされます。
同時に、J2EEは、RemoteインターフェイスHelloと、EJBクラス HelloEJBの情報から、「新しい」EJBオブジェクト・クラスを生成します。先に見たdeploytoolのメッセージ、Compiling wrapper code...に出てくる「wrapper」というのは、元のEJBクラス HelloEJBのメソッド類を引き受けて、1つに包み込んだこの新しいEJBオブジェクト・クラスのことです。これが、J2EEで「コンポーネント」と呼ばれているものの実体です。このコンポーネントのインスタンスは、Homeインターフェイスでのcreateメソッドの呼び出しによって、生成されることになります。
EJBクラスHelloEJBが、Remoteインターフェイスを実装していないにもかかわらず、hello.sayHello()という呼び出しが、EJBクライアントの側から可能なのは、sayHelloメソッドが、実は、EJBクラスHelloEJBのsayHelloメソッドではなく、RemoteインターフェイスHelloを実装した、新しく生成されたEJBオブジェクト内の、書き換えられたsayHelloメソッドであることによって説明されます。このことは、同じ名前、同じ型を持ち、ビジネス・ロジックとして同じ役割を果たすべきこの2つのメソッドが、実装のレベルでは同一のものではない可能性を示唆しています。
こうして、コンテナとコンポーネントという、次のような「メタファー」が成り立つことになります。deploytoolのアイコンにあるような、ビンに豆が1つ入っている状態をイメージしてもいいでしょう。このメタファーは、コンテナとコンポーネントの役割を分かりやすく視覚化するとともに、EJBの3つの定義ファイルの役割も明確にしてくれます。
- コンテナは、コンポーネントの「いれもの」です。コンテナは、最初は空です
- コンテナには、外部との境界に、HomeとRemoteという2つのインターフェイスを
持っています
- Homeインターフェイスを通じて、コンテナにコンポーネントを作ったり、消したり
することが可能です
- コンポーネント内のメソッドは、EJBクラスで定義され、Remoteインターフェイス
を通じて呼び出せます
EJBクラス・ファイル中の、ビジネス・メソッド以外の、ejbで始まる名前を持つ一群のメソッドたちは、主に、コンテナとコンポーネントの関係に関連した仕事を引き受けています。
コンポーネント・メソッドの書き換えと |
先に、コンポーネントのメソッドと、元のEJBクラスのメソッドは同じではないかもしれないことを示唆しましたが、実際にコンポーネントのメソッドは、元のEJBクラスの定義から大きく書き換えられています。すこし模式的になるのですが、コンポーネント内の新しいsayHelloメソッドのつくりを見ることにしましょう。
public String sayHello() throws RemoteException
{ // Remoteメソッド |
メソッドは、Remoteインターフェイスからの呼び出しが可能なように、Remoteインターフェイスを実装したクラスの、リモート・メソッドに書き換えられています。Remoteインターフェイスでこのメソッドが呼ばれると、コンテナが前処理のメソッドを呼び出します。その後で元のEJBクラスのメソッドがそのまま呼び出されます。その後で、再びコンテナが後処理のメソッドを呼び出します。コンテナを主体に考えれば、こうした過程はRemoteでメソッドが呼び出されるたびに、コンテナは呼び出し前の処理と呼び出し後の処理で、コンポーネントのメソッド呼び出しを挟み込む働きをすると解釈できます。
コンテナによるメソッドの挟み込みというテクニックは、単純ですが非常に強力なものです。ある意味では、こうした処理こそがJ2EEの心臓部分と考えてもいいものです。例えば、トランザクションの処理では、メソッドの呼び出しの前後で、いくつかの処理を行わなければなりません。エンティティ・ビーンでは、メソッドの呼び出しの前にデータベースの内容をビーンにロードして、メソッドの呼び出しの後にデータベースにビーンの内容をストアする必要があります。また、J2EEではメソッドごとにさまざまなパーミッションが設定できるのですが、こうした処理はコンテナによるメソッドの挟み込みによって実現することができます。
メソッドの書き換えによって、このように、コンポーネントとコンテナが役割を分担することが可能になります。EJBのプログラマーがビジネス・ロジックに集中することができるのは、こうしたメカニズムによってビジネス・ロジック以外のさまざまな処理をコンテナに担わせることが可能となるからです。
連載内容 | |
J2EEの基礎 | |
第1回 Java Pet Storeで、J2EEを体験する(1) | |
第2回 Java Pet Storeで、J2EEを体験する(2) | |
第4回 J2EEアプリケーションを構成するコンポーネント | |
第5回 データベースのブラウザを作る | |
第6回 EJBにおけるコンテナとコンポーネント | |
第7回 J2EEのセキュリティのキホンを知る | |
第8回 J2EEのトランザクション処理 |
連載記事一覧 |
- 実運用の障害対応時間比較に見る、ログ管理基盤の効果 (2017/5/9)
ログ基盤の構築方法や利用方法、実際の案件で使ったときの事例などを紹介する連載。今回は、実案件を事例とし、ログ管理基盤の有用性を、障害対応時間比較も交えて紹介 - Chatwork、LINE、Netflixが進めるリアクティブシステムとは何か (2017/4/27)
「リアクティブ」に関連する幾つかの用語について解説し、リアクティブシステムを実現するためのライブラリを紹介します - Fluentd+Elasticsearch+Kibanaで作るログ基盤の概要と構築方法 (2017/4/6)
ログ基盤を実現するFluentd+Elasticsearch+Kibanaについて、構築方法や利用方法、実際の案件で使ったときの事例などを紹介する連載。初回は、ログ基盤の構築、利用方法について - プログラミングとビルド、Androidアプリ開発、Javaの基礎知識 (2017/4/3)
初心者が、Java言語を使ったAndroidのスマホアプリ開発を通じてプログラミングとは何かを学ぶ連載。初回は、プログラミングとビルド、Androidアプリ開発、Javaに関する基礎知識を解説する。
|
|