スタンドアロンの Java Swing クライアントからの Web サービスへのアクセス
Inderjeet Singh
課題
Web サービスには、機能豊富なクライアントユーザーインタフェースを念頭に置いてデザインするものがあります。たとえば、GUI を使用して、警報やビデオ会議、その他の機能を提供するインターネットチャット Web サービスなどです。B to C (business to consumer) サービスには、メールやカレンダーサービスなど、リッチなユーザーインタフェースの恩恵を受けるものがあります。Java[tm] Swing API には、そうした機能豊富なスタンドアロンデスクトップクライアントを作成するための強力で移植可能な仕組みが用意されています。この対処法では、スタンドアロンの Java クライアントから Web サービスにアクセスする際の、いくつかの留意事項を取り上げます。
スタンドアロンの Java クライアントから Web サービスにアクセスする際には、次のような考慮事項があります。
- J2EE[tm] コンポーネントと異なり、スタンドアロンクライアントは管理された環境では実行されません。サーブレットやエンタープライズ Bean などの J2EE コンポーネントは、Web サービスにアクセスするための実行時機構 (スタブや動的プロキシ) を提供する仕事をする、そのコンテナによって管理されます。J2EE コンポーネントは自身の利用する Web サービスを宣言し、コンテナは、必要に応じて、そのサービスにアクセスするためのスタブ (または代替機構) をインスタンス化し、設定する仕事をします。スタンドアロンクライアントは管理された環境内では実行されません。このため、Web サービスへのアクセスを自身で管理する必要があります。一般には、この管理は、WSDL 内の指定内容に従って Web サービスにアクセスするためにスタブ、またはその他必要なクラスをインスタンス化し、設定するなどの処理です。セキュリティー保護のために HTTPS や基本認証を利用するようにスタブを設定する必要があることもあります。
- Java では、JAX-RPC (Java API for XML-based RPC) API を使用して、Web サービスにアクセスします。JAX-RPC テクノロジはまだ Java Runtime Environment (JRE) 規格に組み込まれていないため、クライアントが JAX-RPC API を利用するには、JAX-RPC 実装クラスを使用できるようにする必要があります。J2EE 1.4 には、JAX-RPC テクノロジが含まれているため、J2EE コンポーネントの場合は、JAX-RPC 実行時環境はコンテナを使って提供されます。これに対し、Swing クライアントの場合、JAX-RPC API を利用するには、そのクラスパス上で JAX-RPC 実装クラスにアクセスできるようにする必要があります。
- スタンドアロンの JAX-RPC クライアントは、3 つある JAX-RPC 通信モード (スタブ、動的プロキシ、および DII) のどれでも使用できます。これらのモードが開発の容易さや移植性、WSDL の可用性に及ぼす影響は、それぞれに異なります。開発者は、どの通信モードが自分のニーズに最も適しているかを判断する必要があります。
- WSDL から生成されるサービスエンドポイントインタフェースと、JAX-RPC コンパイラが WSDL の生成に使用する基のインタフェースとが異なることがあります。これは、XML スキーマの型と Java 言語の型とが正確には対応していないためです。たとえば、
xsd:date
および xsd:dateTime
はともに java.util.Calendar
にマッピングされます。生成されるサービスインタフェースはまた、使用されるエンコーディング形式にも依存します。たとえば Web サービスが document-literal エンコーディングを使用している場合、生成される Java インタフェースには、どのクラスもラップされたバージョンが含まれます。このことは、メソッドパラメータが String
などの単純型の場合、そのパラメータは、String
型のプロパティー 1 つを含むオブジェクトでラップされることを意味します。この結果、インタフェースが不必要に複雑になるため、生成されたインタフェースを使用する開発者には、困難が生じる場合があります。
- より高いレベルのデザイン目標として、サービスの WSDL への依存を減らすことを考えてください。これは、サービスの進化に伴ってクライアントコードが受ける影響を最小限に抑えるのに役立ちます。
対処法
次の表は、スタンドアロンクライアントからの Web サービスへのアクセスする際の推奨事項をまとめています。これら推奨事項については、表のあとで詳しく説明します。
以下では、これらの問題点を個々に見ていきます。最初に取り上げるのは、通信モードの選択の問題です。
通信モードの選択
スタンドアロンクライアントにもっとも適した通信モードはスタブです。これは、DII や動的プロキシと比較して使い易いことにあります。これら 3 つの方法については、『Designing Web Services with the J2EE 1.4 Platform』の 5.3.1 節と 5.3.2 節で比較しています。
スタブの利用
スタブは、サービスの WSDL に JAX-RPC コンパイラを実行すると生成されます。スタブの生成では、必ず、JAX-RPC コンパイラが WS-I 基本プロファイル互換の document-literal エンコーディングを使用し (-f:wsi
スイッチを使用)、パラメータのラップを解除する (-f:unwrap
スイッチを使用) ように設定してください。これは、Ant ターゲットで行うこともできます。たとえばコード例 1は、WSDL ファイルからスタブクラスを生成します。
<taskdef
name="wscompile"
classname="com.sun.xml.rpc.tools.ant.Wscompile">
<classpath
refid="jaxrpc.classpath"/>
</taskdef>
<wscompile gen="true"
base="${output.classes.dir}" features="explicitcontext,wsi,unwrap"
keep="true" debug="true"
config="${jaxrpc.client.config.dir}/client-config.xml">
<classpath
refid="jaxrpc.classpath"/>
</wscompile>
コード例 1: スタブクラスを生成する Ant タスク
この例の client-config.xml
ファイルには、WSDL ファイルのある場所へのポインタが含まれています。また、スタブ用に生成されたクラスの Java ソースコードが残されるよう、keep
属性が true
に設定されていることにも注意してください。生成されたスタブインタフェースの内容、そしてその使用方法をクライアント開発者が理解するには、このソースコードが必要です。生成された Java ソースコードに基づいて、スタブおよび関係するクラスを理解できます。サービスに対する呼び出し方法を理解するにあたり、クライアント開発者はこのソースコードを直接利用することも、あるいはこのソースコードから Javadoc[tm] を生成することもできます。
このスタンドアロンクライアントは生成されたスタブクラスをインスタンス化して、Web サービスにアクセスするように設定します。コード例 2は、スタブを使って Web サービスにアクセスする方法を示しています。
StringPurchaseOrderService_Impl svc = new
StringPurchaseOrderService_Impl();
StringPurchaseOrderServiceSEI poservice =
svc.getStringPurchaseOrderServiceSEIPort();
((Stub)poservice)._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY,
"http://localhost:8080/webservice/StringPurchaseOrderService");
String purchaseOrder = ... // これは、XML における注文書の文字列型表現
String result = poservice.submitPO(purchaseOrder);
コード例 2: スタンドアロンクライアントでのスタブの利用
StringPurchaseOrderService_Impl
には異なる JAX-RPC 実行環境間の移植性はありませんが、推奨する方法はこの方法であることに注意してください。これは、移植可能なほかのどの方法と比べても、かなり使い易いためです。これは、完全に移植可能なコードを作成できる J2EE クライアントの場合と異なります。また、Java プラットフォームから見ると、スタンドアロンクライアント自体が移植可能であることに注目することも大切です。このクライアントは、各種ハードウェアプラットフォームおよびオペレーティングシステムで変更なしに利用できます。単に同じ JAX-RPC 実行時環境が必要なだけであり、JAX-RPC が Java SE プラットフォームに組み込まれていない以上、この実行時環境を、アプリケーションとともに含める必要があります。スタブクラスの移植性というのは、J2EE 5 プラットフォームで利用できるようになる JAX-RPC 2.0 仕様に明記されている目標の 1 つです。
使用する Web サービスの URL をクライアントにハードコーディングしないのは、よくあることです。ときとして、実行時にユーザー入力に基づき、サービスの URL を選択するようになっていることがあります。これは、上記の例に示すように、スタブを利用し、実行時に Stub.ENDPOINT_ADDRESS_PROPERTY
に目的のサービスの URL を設定することによって実現できます。
動的プロキシの利用
動的プロキシは、移植できないという性格上、スタブを利用できない場合に使用することを推奨します。こうしたケースは、実行時に使用可能な JAX-RPC 実装とコンパイル時の JAX-RPC 実装とが異なる可能性がある場合に発生することがあります。コード例 3は、スタンドアロンクライアントでの動的プロキシの使用法を示しています。
ServiceFactory
sf = ServiceFactory.newInstance();
URL wsdlURL = new URL("
http://localhost:8080/webservice/StringPurchaseOrderService
?WSDL");
QName serviceQname = new
QName("urn:StringPurchaseOrderService", "StringPurchaseOrderService");
Service s = sf.createService(wsdlURL,
serviceQname);
QName portQname = new
QName("urn:StringPurchaseOrderService",
"StringPurchaseOrderServiceSEIPort");
StringPurchaseOrderServiceSEI poservice =
(StringPurchaseOrderServiceSEI) s.getPort(portQname,
StringPurchaseOrderServiceSEI.class);
String purchaseOrder = ... // これは、XML における注文書の文字列型表現
SubmitPO param = new SubmitPO(purchaseOrder);
SubmitPOResponse response = poservice.submitPO(param);
String result = response.getResult();
コード例 3: スタンドアロンクライアントでのスタブの利用
動的プロキシは、便利さでは勝るラップ解除されたバージョンではなく、ラップされたバージョンのサービスエンドポイントインタフェースを使用する必要があることに注意してください。これは、Web サービスが rpc
形式の SOAP バインドではなく、WS-I 基本プロファイル準拠の document
を使用するためです。document
のバインドはラップされたバージョンのインタフェースに対応しています。wscompile は、スタブクラスを使ってラップ解除されたバージョンにアクセスする便利な手段を追加しますが、
動的プロキシがスタブクラスを利用することはなく、このため、ラップされたバージョンを使用する必要があります。ラップされたバージョンのサービスエンドポイントインタフェースは、wscompile を呼び出して WSDL からクラスを生成するときに unwrap
スイッチを省略すると生成できます。動的プロキシコードの場合も、依然、WSDL からのクラスの生成が必要であることに注意してください。ただし、アクセする必要があるのは、StringPurchaseOrderServiceSEI
、SubmitPO
、および SubmitPOResponse
などの、サービスエンドポイントインタフェースに関係するクラスだけです。
DII (Dynamic Invocation Interface) の利用
DII を利用するケースはまれです。DII は、クライアントが WSDL にアクセスすることはないが、呼び出すメソッドを何らかの形で認識しておく必要がある場合に利用できます。コード例 4は、スタンドアロンクライアントでの DII の使用法を示しています。
ServiceFactory
sf = ServiceFactory.newInstance();
URL wsdlURL = new URL("
http://localhost:8080/webservice/StringPurchaseOrderService
?WSDL");
QName serviceQname = new QName("
urn:StringPurchaseOrderService",
"StringPurchaseOrderService");
Service s = sf.createService(wsdlURL, serviceQname);
QName portQname = new
QName("
urn:StringPurchaseOrderService",
"StringPurchaseOrderServiceSEIPort");
Call call = s.createCall(portQname);
call.setTargetEndpointAddress(serviceUrl);
call.setProperty(Call.SOAPACTION_USE_PROPERTY, new
Boolean(true));
call.setProperty(Call.SOAPACTION_URI_PROPERTY,"");
// call.setOperationName(new QName(NS_BODY, "submitPO"));
//を呼び出すことによってオペレーション名を設定する必要がないことに注意
// これは、Web サービスの使用する SOAP バインドが rpc ではなく、document であるため。
// WS-I 準拠の document-literal の場合、エンコーディングとして "" を指定することによってエンコーディング形式を
//literal、オペレーション形式を document に設定する必要がある
call.setProperty("javax.xml.rpc.encodingstyle.namespace.uri", "");
call.setProperty(Call.OPERATION_STYLE_PROPERTY,
"document");
// 要求パラメータおよび戻り値の型は WSDL ファイルそのものに
// 定義されているため、それらの qnames は本体の名前空間で定義される
QName requestQname = new
QName("
urn:StringPurchaseOrderService", "submitPO");
QName responseQname = new
QName("urn:StringPurchaseOrderService",
"submitPOResponse");
// DII 呼び出しの戻り値の型を定義。
// SubmitPOResponse は、Web サービスによって送信されるラップされた型に一致する必要がある。
call.setReturnType(responseQname,
SubmitPOResponse.class);
// DII 呼び出しのメソッドパラメータの型を定義。
// WSDL ファイルでは、submitPO のメッセージ部の名前は "parameters"
// このため、要求パラメータは次の方法で定義される。
call.addParameter("parameters", requestQname,
SubmitPO.class, ParameterMode.IN);
String purchaseOrder = ... // これは、XML での注文書の文字列型表現
SubmitPO param = new SubmitPO(purchaseOrder);
Object[] params = {param};
// DII 呼び出しを呼び出す
SubmitPOResponse response = (SubmitPOResponse)
call.invoke(params);
String result = response.getResult();
コード例 4: スタンドアロンクライアントでの DII の利用
上記の例に示すように、DII の利用は非常に複雑になることがあり、このため、特殊な状況でのみ使用することを推奨します。J2EE 1.4 SDK に用意されている JAX-RPC 実装には、Web サービスメソッドのパラメータ型が string
型の場合にラッパークラスが正しく生成されないバグもあります。開発者は、string_1
ではなく、String_1
になるようにメンバー変数名を変更する同等のクラスで、生成されたラッパークラス SubmitPO を上書きすることによって、このバグを回避する必要があります。
JAX-RPC 実行時環境も、アプリケーションとともに含める必要があります。これは、インストールされているアプリケーションサーバーから必要なクラスをコピーすることによって行うことができます。このバンドルを行うときは、アプリケーションの再配布でのライセンスの問題に気をつけてください。コード例 5に示す Ant 構築ファイルからの抜粋は、J2EE SDK でこのコピーを行う方法を示しています。
<target
name="copy-jaxrpc-runtime" depends="init">
<unjar
src="${j2ee.home}/lib/j2ee.jar" dest="${build.classes.dir}"/>
<unjar
src="${j2ee.home}/lib/jaxrpc-api.jar"
dest="${build.classes.dir}"/>
<unjar
src="${j2ee.home}/lib/jaxrpc-impl.jar"
dest="${build.classes.dir}"/>
<unjar
src="${j2ee.home}/lib/saaj-impl.jar"
dest="${build.classes.dir}"/>
<unjar
src="${j2ee.home}/lib/mail.jar" dest="${build.classes.dir}"/>
<unjar
src="${j2ee.home}/lib/dom.jar" dest="${build.classes.dir}"/>
<unjar
src="${j2ee.home}/lib/xercesImpl.jar"
dest="${build.classes.dir}"/>
</target>
コード例 5: JAX-RPC 実行時環境をコピーする Ant タスク
エラーの処理
Web サービスクライアントは、システム例外とサービス固有の例外の 2 種類のエラーに対処する必要があります。
システム例外は、ネットワーク接続不良や、Web サービスが停止しているなどの回復不可能なシステムエラーが原因で発生します。JAX-RPC クライアントは、システム例外を RemoteException
として受け取ります。サーバー固有の例外は Web サービスによってスローされ、不正なパラメータが渡されたり、データベースに重複キーが作成されようとしたりしたなどのエラーがアプリケーションロジックで発生したことを示します。JAX-RPC クライアントは、サービス固有の例外をアプリケーション例外クラスとして受け取ります。これらの例外は、WSDL に指定されたフォルトを基に JAX-RPC コンパイラによって生成されます。
スタンドアロンクライアントにおける例外処理は、しばしば、サーブレットやエンタープライズ Bean によって Web サービス呼び出しが行われた場合よりも簡単になります。これは、スタンドアロンクライアントの他方にはユーザー (人間) がいるのが一般的で、ユーザーがエラーの状態に対処できるためです。このため、簡単な例外処理方法として、ユーザーが理解するメッセージに例外状態を変換して、操作をやり直すか、対策を取るように促すようにすることが考えられます。サービス固有の例外は、しばしば、Web サービスの呼び出しを行う前にパラメータの妥当性を検査することによって回避あるいは減らすことができます。
Web サービスアーティファクトからのアプリケーションロジックの分離
優れたデザインでは、Web サービスクライアントのアプリケーションロジックがサービスの WSDL にできるかぎり依存しないようにします。これは、サービスの進化に伴ってクライアントコードが受ける影響を最小限に抑えるのに役立ちます。この分離は、スタブおよび関係する生成クラスを使用するコードをカプセル化する委託クラスを作成することによって実現できます。委託の公開 API はどのスタブクラスも伝達することなく、代わりに、アプリケーションのほかの部分が理解するクラスにそれらクラスをマッピングします。
ローカルでのキャッシュへのデータ書き込み
一般的な Swing クライアントは、ユーザーに対して非常に応答性の優れた機能豊富なユーザーインタフェースを提供します。Swing クライアントは、ネットワークを介さずにローカルにキャッシュにデータを書き込むことによって、ユーザーインタフェースの応答性を高めることもできます。ただし、これには、データの一貫性を保持するという問題が伴います。クライアントおよび Web サービスともに、同じプロトコル (SyncML など) を使用してデータの同期を維持するようにデザインする必要があります。
ブラウザを使用するクライアントがデスクトップクライアントより好まれる大きな理由の 1 つは、最初にインストールすることなく、インターネットに接続されている任意のマシンから利用できることにあります。スタンドアロン Java クライアントの配備は、Java プラットフォームに含まれている Java Web Start テクノロジを使って管理できます。ただし、Web サービスクライアントで Java Web Start テクノロジを利用することに伴う問題もいくつかあります。
- JNLP ファイルを作成し、スタンドアロンクライアントアプリケーションの JAR ファイルとともに Web サイトで利用できるようにする必要があります。これらのファイルの提供場所としては、Web サービスを提供する Web サイトが優れた選択肢と考えられます。クライアントアプリケーションは、接続する必要がある Web サービスの URL を知っている必要があるため、たとえば、JSP ファイルを使用して、JNLP ファイルを動的に生成するのがベストです。
- クライアントアプリケーションは、Java Web Start が提供するサンドボックス内で実行されます。このことは、ローカルのファイルシステムへのアクセスやネットワーク接続を開くなどの処理で機能が制限されることを意味します。これは、アプリケーションが使用するすべての JAR ファイルに署名することで、対処できます。
- Web サービスにアクセスするスタンドアロン Java クライアントが Web サービスの呼び出しを行うには、JAX-RPC 実装が必要です。JAX-RPC 実行時環境を提供する方法の 1 つは、Java Web Start が必要に応じてダウンロードできるよう、必要な JAR ファイルを JNLP ファイルに指定する方法です。ただし、署名の入っていない JAR ファイルがある場合、JAX-RPC 実装はサンドボックス内で正しく動作できません。この問題に対処するもう 1 つの方法としては、ユーザーが自分のマシンに JAX-RPC 実行時環境をインストールするよう奨励する方法があります。
参考資料
この項目に関する詳細は、次の資料を参照してください。
© 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.