J2EE コンポーネントからの Web サービスへのアクセス

Sean BrydonSmitha Kangath

課題

J2EE[tm] アプリケーションで、Web サービスへのアクセスが必要になることがあります。そうした場合、サーブレットや JavaServer[tm] (JSP[tm]) ページ、メッセージ駆動型 Bean、エンタープライズ Bean などの J2EE コンポーネントは、Web サービスのクライアントの働きをします。JAX-RPC (Java[tm] API for XML-based RPC) テクノロジには、標準の Web サービスアクセス手段が用意されており、Web サービスアクセスモードとして、スタブ、動的プロキシ、DII (dynamic invocation interface) の 3 つのモードがあります。このドキュメントでは、自身の使用する 1 つまたは一群のサービスをクライアントがどこで認識するのかを示すユースケースに焦点を当てます。サービスの動的な検出、アクセスについては取り上げません。Web サービスにアクセスするさまざまな方法とその特徴が提示されます。

考慮事項としては、次のような事項が挙げられます。

対処法

Web サービスのクライアントとして J2EE コンポーネントを開発する際の主な問題点をいくつか考えてみましょう。これらの問題点は、サーブレットなどの Web コンポーネントあるいは EJB コンポーネントのどちらを使用するにしてもほぼ同じです。

最初に、J2EE コンポーネントから Web サービスへの呼び出しを簡単に概観してみましょう。J2EE Web サービスクライアントプログラミングモデルのことにすでに習熟している場合は、このあとの復習を省略してかまいません。復習のあとで、J2EE コンポーネントから Web サービスへのアクセスに関するほかのいくつかの問題点を検討します。

J2EE コンポーネントから Web サービスへのアクセスの概要

J2EE コンポーネントから Web サービスへの呼び出しは、基本的に以下から構成されます。
基本的に、ターゲットサービスの WSDL ファイルに基づき、クライアントは、ターゲットサービスへの Java インタフェースや、ターゲットサービスインタフェースで渡されるパラメータ型といったオブジェクトのサポートクラスなどのアーティファクトを生成します。クライアントの配備記述子には、サービスに対する service-ref が含まれる必要があります。クライアントコードは JNDI (Java Naming and Directory Interface[TM] ) を使ってそのサービス参照をルックアップし、生成されたインタフェースを使用してサービスを呼び出します。

以下は、コード例の抜粋で、別のアプリケーションの Web サービスにアクセスする Web コンポーネントのコードを示しています。ターゲット Web サービスにアクセスする EJB コンポーネントのコードも、このコード例と同じになります。ターゲット Web サービスの WSDL ファイルには、XML 注文書を受け付けるためのサービスインタフェースが記述されていて、注文書を送信するメソッドがあります。サービスの WSDL に基づき、クライアントは、ターゲットサービスを表す PurchaseOrderServiceSEI.java インタフェースと、サービスの submitPO メソッドが求めるパラメータ型である PurchaseOrder.java クラスという JAX-RPC クラスを生成します。

import javax.naming.*;
import javax.xml.rpc.*;

....
Context ic = new InitialContext();
Service purchaseOrderSvc =
    (Service) ic.lookup("java:comp/env/service/PurchaseOrderService");
PurchaseOrderServiceSEI port = (PurchaseOrderServiceSEI)
    purchaseOrderSvc.getPort(PurchaseOrderServiceSEI.class);

.....

// ターゲットサービスの submitPO メソッドのパラメータ型用に
//生成された注文書クラスを使用し、注文書オブジェクトを作成し、
//その値を設定
PurchaseOrder po = new PurchaseOrder();
po.setPoId(poID);
...
//ここでサービスを呼び出す
String ret = port.submitPO(po);

コード例 1: Web サービスにアクセスする、J2EE コンポーネントのコード

上記の抜粋コードの Web サービスの呼び出しが、J2EE における管理対象オブジェクトの呼び出しに似ていることに注意してください。JNDI を使って、サービスオブジェクトへの参照をルックアップして取得し、続いて特定のポートオブジェクトを取得し、そのポートオブジェクトでメソッドを呼び出します。J2EE コンポーネントはコンテナ内で実行され、そのため、JNDI を使ってリソースをルックアップする際にコンテナサービスにアクセスします。

配備記述子には、アクセス先の Web サービスに対するサービス参照要素が必要です。たとえば、サービスにアクセスするサーブレットまたは JSP コンポーネントの web.xml ファイルには、アクセス先のサービスごとにサービス参照が 1 つ必要になります。以下は、web.xml ファイルからの抜粋で、サービス参照要素を示しています。

<service-ref>
<description>String Purchase Order Service Client</description>
<service-ref-name>service/PurchaseOrderService</service-ref-name>
<service-interface>
com.sun.j2ee.blueprints.docoriented.client.poservice.PurchaseOrderService
</service-interface>
<wsdl-file>WEB-INF/wsdl/PurchaseOrderService.wsdl</wsdl-file>
<jaxrpc-mapping-file>WEB-INF/purchaseorderservice-mapping.xml</jaxrpc-mapping-file>
<service-qname xmlns:servicens="urn:PurchaseOrderService">servicens:PurchaseOrderService</service-qname>
</service-ref>

コード例 2: web.xml 配備記述子におけるサービス参照

アプリケーションサーバーに固有の配備記述子には、アセンブリおよび配備時にこのサービス参照を配備環境内の値にマッピングする情報も必要です。たとえば J2EE SDK では、sun-web.xml ファイルを使用します。以下は、sun-web.xml 配備記述子の例です。

<service-ref>
<service-ref-name>service/PurchaseOrderService</service-ref-name>
<port-info>
<service-endpoint-interface>
com.sun.j2ee.blueprints.docoriented.client.poservice.PurchaseOrderServiceSEI
</service-endpoint-interface>
<stub-property>
<name>javax.xml.rpc.service.endpoint.address</name>
<value>http://localhost:8080/webservice/PurchaseOrderServiceBean </value>
</stub-property>
</port-info>
</service-ref>

コード例 3: J2EE SDK sun-web.xml 配備記述子におけるサービス参照のマッピング

J2EE コンポーネントから Web サービスへのアクセスに関する問題点

ここまでは、J2EE コンポーネントから Web サービスへのアクセスの基本的なプログラミングモデルの復習です。ほかのいくつかの問題点を検討してみましょう。

WSDL の利用

クライアントのデザインおよび実装の工程は、そのクライアントがアクセスする各サービスの WSDL ファイルを検討することから始まります。WSDL は、サービスにアクセスするために必要なインタフェース、メソッド、パラメータ、および例外をクライアントに提供します。また、WSDL には、サービスのアドレスなどの配備情報が含まれることもあります。ただし、WSDL はサービスの完全な記述ではない場合があることに注意してください。サービスへの全アクセス要件を理解するには、追加の情報が必要になることがあります。たとえば、WSDL には、サービスには基本認証または相互認証が必要であることを示す相互運用可能な手段がありません。このため、クライアントの開発者は、WSDL ファイル以外のソースからそうしたサービス要件を入手する必要があります。

もう 1 つの考慮事項は、クライアントが WSDL を取得し、保持する場所です。動的な検出については取り上げないため、ここでは、WSDL ファイルを入手する場所およびそのアクセス可能な場所を、クライアント開発者が知っているものと想定しています。サービスの WSDL は、配備され、特定の URL から入手できることがありますが、クライアントコードの開発者は、その WSDL ファイルのコピーを作成し、そのコピーを開発作業スペースに組み込む必要があります。クライアントはこの WSDL から始まるため、一般に、プログラムでサービスをアクセスするのに必要なクライアントアーティファクトの開発スタイルから WSDL から Java (WSDL to Java) の方向になります。クライアント開発ツールは、WSDL ファイルに基づいて、WSDL ファイルに記述されているサービスを表すいくつかの Java クラスを生成します。このターゲットサービスの呼び出しを行う J2EE コンポーネントは、生成されたそれらのクラスを使用します。クライアントアプリケーションは、構築時に WSDL ファイルを使用するばかりでなく、移植可能なパッケージの構成要素として WSDL ファイルを含める必要があります。

また別の考慮事項として、クライアントコードの開発者が WSDL ファイルを使用して、ターゲットサービスインタフェースを表すクライアント側クラスを生成した場合に、サービスの WSDL ファイル内の型に対応する Java 型が、求められている型と異なることがあるという問題があります。たとえば、日付型が求められているのに、Java のカレンダ型のことがあります。WSDL の型と Java の型のマッピングは、ツールは JAX-RPC のバージョンによって少し異なることがあります。

構造の追加とパターンの利用

どのアプリケーションのデザインでもそうであるように、Web サービスクライアントのデザインでは、構造からメリットを得られることがあります。クライアントコードにおける、Web サービス対話コードとその他のコードの分離は推奨事項です。推奨するプログラミング慣行の 1 つに、ビジネス委託コードを適用し、クライアントコードから Web サービスへのアクセスをカプセル化することがあります。ビジネス委託は、Web サービスへのアクセスをカプセル化し、クライアントコードからの Web サービス対話コードの分離を適用するのに役立ちます。ServiceLocator パターンを利用することも、一般的なルックアップコードをカプセル化することによってコードを整理し、アプリケーションをより管理しやすくするのに役立つことがあります。詳細は、Java BluePrints Solutions Catalogの別のエントリで詳しく取り上げている、Web サービスのサポートのためのサービスロケータの更新を参照してください。

通信モードの選択

Web サービスにアクセスする J2EE の場合は、JNDI ルックアップから取得したスタブを利用するのが最善です。ただし、そのためのアクセスコードは、生成されたスタブクラスに依存しないようにすることができます。スタブクラスにはアプリケーションサーバー間の移植性がないため、このことは重要です。このあとのコード例 4 は、J2EE アプリケーションがスタブを使ってサービスにアクセスする方法を示しています。このアプリケーションは、JNDI の InitialContext.lookup 呼び出しを使ってサービスを探します。JNDI 呼び出しからは、OpcOrderTrackingService オブジェクト (つまり、スタブ) が返されます。

  Context ic = new InitialContext();
  StringPurchaseOrderService_Impl svc = (StringPurchaseOrderService_Impl) ic.lookup("java:comp/env/service/StringPurchaseOrderService");
  StringPurchaseOrderServiceSEI poservice = svc.getStringPurchaseOrderServiceSEIPort();
  String purchaseOrder = ... // これは、XML における注文書の文字列型表現
   String result = poservice.submitPO(purchaseOrder);

コード例 4: J2EE 環境におけるスタブへのアクセス (非推奨)

上記のクライアントコードは生成されたスタブクラスに依存するため、推奨できるスタブの使用方法ではありません。この例は、問題なく機能しますが、JAX-RPC には、サービスにアクセスして、同じ結果を得る問題のない方法が用意されています。JAX-RPC の javax.xml.rpc.Service インタフェースメソッド getPort を利用することによって、スタブまたは動的プロキシのどちらを使用するかに関係なく同じ方法で Web サービスにアクセスできます。getPort メソッドは、生成されたスタブ実装クラスのインスタンスか動的プロキシのいずれかを返すため、クライアントは、このインスタンスを利用して、サービスエンドポイントでオペレーションを呼び出すことができます。コード例 5 は、生成されたスタブクラスに対する依存を解消する方法を示しています。

  Context ic = new InitialContext();
  Service svc = (Service) ic.lookup("java:comp/env/service/StringPurchaseOrderService");
 StringPurchaseOrderServiceSEI poservice = (StringPurchaseOrderServiceSEI) svc.getPort(StringPurchaseOrderServiceSEI.class);
  String purchaseOrder = ... // これは XML における注文書の文字列型表現
   String result = poservice.submitPO(purchaseOrder);

コード例 5: J2EE 環境におけるスタブへのアクセス (推奨)

このコードでは、JNDI 参照をサービス実装クラスにキャストするのではなく、javax.xml.rpc.Service インタフェースにキャストしています。サービスエンドポイントインタフェースのクライアント側表現は、getPort メソッドを呼び出すことによって取得されます。ポートを取得すると、クライアントは、アプリケーションが求めるあらゆる呼び出しをそのポートで行うことができます。上記のコードが呼び出されると、JAX-RPC 実行時環境はそのポートと通信するためのポートおよびプロトコルバインドを選択し、返されたスタブ (サービスエンドポイントインタフェースを表すスタブ) を設定します。さらに、J2EE プラットフォームでは、サービスの複数のポートを配備記述子に指定することができるため、コンテナは、その設定に基づいて、サービスの呼び出しに使用できるプロトコルバインドおよびポートから最適なものを選択できます。

ターゲットサービスの URL へのバインド

ターゲット Web サービスが URL に配備されるため、クライアントコードがそのサービスにアクセスするには、サービス参照をその URL にバインドする必要があります。アプリケーションの要件によっては、クライアントは、さまざまなタイミングでサービスの URL にサービス参照をバインドできます。

例外の処理

クライアントから Web サービスへのアクセスでは、システム例外とサービスに固有の例外 (サービスエンドポイントからスローされる例外) の 2 種類の例外が発生します。クライアントアプリケーションは、両方の種類の例外に対処する必要があります。

システム例外は、サービスメソッドを呼び出すときに不正なパラメータが渡されたり、サービスにアクセスできなかったり、ネットワークエラーが発生したり、その他、アプリケーションが制御できないエラーが発生したりした場合にスローされます。一般に、サービスへの呼び出しをやり直す以外に、クライアントがシステム例外でできることは多くありません。通常、Web サービスのクライアントはツールを使用し、ターゲットサービスの WSDL ファイルに対応するクライアント側インタフェースを JAX-RPC に生成してもらいます。こうして生成され、クライアント側コードで使用されるインタフェースは、システム例外でリモート例外をスローします。このインタフェースを使用してサービスを呼び出すコードは、こうしたリモート例外をキャッチする必要があります。

public interface PurchaseOrderServiceSEI extends java.rmi.Remote {
    public java.lang.String submitPO(
PurchaseOrder purchaseOrder_1) throws java.rmi.RemoteException;
}
コード例 6: サービスに対応して生成されたクライアント側インタフェース - システム例外がリモート例外にマッピングされていることを示す

フォルトからマッピングされるサービス例外は、サービス固有のエラーが検出されたときにスローされます。サービス例外はターゲット Web サービスで定義され、WSDL ファイルで伝達されます。サービス固有の例外としては、たとえば、送信されたデータの妥当性検査でエラーが検出され、無効なデータ例外がスローされるなどがあります。ターゲットサービスの WSDL ファイルに対応するクライアント側インタフェースおよびクラスの生成では、サービス例外は、サービス例外のクライアント側表現としても生成されます。多くの場合、クライアントコードは対策を取り、修正したデータを使って要求を再送信することによって、こうしたサービス固有の例外を回復することができます。ここで重要なことは、サービスにアクセスするクライアントのデザインでは、ターゲットサービスの WSDL ファイルに指定されたこれら 2 種類の例外を考慮し、それら例外を処理するクライアントコードをデザインする必要があるということです。

移植性のためのパッケージング

Web サービスのクライアントの働きをする J2EE コンポーネントは、ほかの J2EE アプリケーションと同じようにパッケージに仕上げます。ここで重要なことは、Web サービスにアクセスする J2EE コンポーネントを含むこうしたアプリケーションは移植可能であることです。必要なパッケージングはかなり単純です。クライアントが Web アプリケーションの場合、クライアントは、web.xml などの一般的な Web アーティファクトばかりでなく、Web サービスアクセスコードに固有のいくつかの追加アーティファクトとともに WAR ファイルにパッケージングされます。同様に、EJB コンポーネントは、ejb-jar.xml ファイルなどの一般的な EJB アーティファクトばかりでなく、Web サービスアクセスコードに固有のいくつかの追加アーティファクトとともに ejb-jar ファイルにパッケージングされます。

J2EE 環境で動作する Web サービスクライアントには、次のような追加のアーティファクトが必要です。

セキュリティー要件に対する対処

ターゲット Web サービスに、セキュリティー要件があることがあります。セキュリティー要件は WSDL ファイルに指定されていることがあります。また、サービスエンドポイントで求められていても、WSDL ファイルに指定されていないこともあります。いずれにしても、ターゲットサービスにセキュリティー要件がある場合、クライアントはその要件に対処する必要があります。セキュリティー要件としては、たとえば、ターゲットエンドポイントは SSL 経由でアクセス可能である、ターゲットサービスに基本認証が指定されているためにクライアントはユーザー名とパスワードを提供する必要がある、ターゲットサービスで相互認証が求められていてクライアントはデジタル証明書も提供する必要がある、などがあります。また、ターゲットサービスがメッセージレベルのセキュリティーを利用していることもあります。こうした要件 (ユーザー名とパスワードの提供、SSL のためのターゲットサービスの証明書の処理、相互認証のためのターゲットサービスへのクライアントのデジタル証明書の提供など) に対処するためのクライアント設定はすべてアプリケーションサーバーによって異なり、このため、ここではその詳細を取り上げません。Web サービスを呼び出す際のセキュリティー環境の構成については、使用しているアプリケーションサーバーのマニュアルを参照してください。

クライアントコードは、その中で必要なセキュリティーアーティファクトを提供することによってターゲットサービスのセキュリティー要件に対処することができますが、クライアントコード内でセキュリティーを扱うのは回避するようにしてください。代わりに、クライアントはアプリケーションサーバー構成ツールを使用し、ターゲットサービスのセキュリティー要件に対処するようサービス参照を設定すべきです。そうすることによって、J2EE コンテナは、設定されたサービス参照のセキュリティーを管理し、実行時、ターゲットサービスに呼び出しが行われときに必要なアーティファクトを提供できます。ターゲットサービスのセキュリティー要件にコードで対処する例としては、たとえば、サービスに基本認証が指定されている場合に、クライアントコードがスタブプロパティーにユーザー名とパスワードを設定するなどがあります。この場合は、代替方法として、クライアントアプリケーションのサービス参照で基本認証を管理するようにアプリケーションサーバーを設定する方法を推奨します。こうすることによって、ユーザー名およびパスワードの提供を、クライアントコードではなく、アプリケーションサーバーで処理でき、コードが整理されたものになります。

参考資料

この項目に関する詳細は、次の資料を参照してください。
© Sun Microsystems 2005. All of the material in The Java BluePrints Solutions Catalog is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.