スタンドアロンの Java Swing クライアントからの Web サービスへのアクセス

Inderjeet Singh

課題

Web サービスには、機能豊富なクライアントユーザーインタフェースを念頭に置いてデザインするものがあります。たとえば、GUI を使用して、警報やビデオ会議、その他の機能を提供するインターネットチャット Web サービスなどです。B to C (business to consumer) サービスには、メールやカレンダーサービスなど、リッチなユーザーインタフェースの恩恵を受けるものがあります。Java[tm] Swing API には、そうした機能豊富なスタンドアロンデスクトップクライアントを作成するための強力で移植可能な仕組みが用意されています。この対処法では、スタンドアロンの Java クライアントから Web サービスにアクセスする際の、いくつかの留意事項を取り上げます。

スタンドアロンの Java クライアントから Web サービスにアクセスする際には、次のような考慮事項があります。

対処法

次の表は、スタンドアロンクライアントからの Web サービスへのアクセスする際の推奨事項をまとめています。これら推奨事項については、表のあとで詳しく説明します。

問題点
推奨
通信モードの選択 スタブを利用します。-f:unwrap を付けてパラメータのラップを解除した WS-I 基本プロファイル互換モード (-f:wsi を使用) で WSDL からサービスインタフェースを生成します。
JAX-RPC 実行環境の提供 アプリケーションサーバーから JAX-RPC 実装 JAR を取り込みます。
エラーの処理 システム例外については、ユーザーが代わりのサービスを再試行または選択できるようにし、サービス固有の例外については、対策を取れるようにします。
Web サービスアーティファクトからのアプリケーションロジックの分離 委託を使用して、Web サービスに対するすべての呼び出し、および Web サービスに使用するすべてのパラメータをカプセル化します。
Web サービスのオーバーヘッドが原因の遅延を回避することによる応答性に優れた GUI の作成 Web サービスに対するきめの粗い呼び出しを行なって、受信データをキャッシュに書き込み、ローカルアクセスできるようにします。
クライアントの自動配備 スタンドアロンクライアントで Java Web Start を使用し、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 からのクラスの生成が必要であることに注意してください。ただし、アクセする必要があるのは、StringPurchaseOrderServiceSEISubmitPO、および 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 実行時環境の提供

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 など) を使用してデータの同期を維持するようにデザインする必要があります。

配備での Java Web Start の利用

ブラウザを使用するクライアントがデスクトップクライアントより好まれる大きな理由の 1 つは、最初にインストールすることなく、インターネットに接続されている任意のマシンから利用できることにあります。スタンドアロン Java クライアントの配備は、Java プラットフォームに含まれている Java Web Start テクノロジを使って管理できます。ただし、Web サービスクライアントで Java Web Start テクノロジを利用することに伴う問題もいくつかあります。

参考資料

この項目に関する詳細は、次の資料を参照してください。


© 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.