目次


ヒント: JAX-RPCでSOAPメッセージを送受信する

JAX-RPCはメッセージ交換のより使い易い方法を提供します

Comments

相互運用性(インターオペラビリティ)は、Webサービスの基盤の一つです。これは、Webサービスがメッセージを標準的な形式で送受信することを意味します。一般的に、そのフォーマットとはSOAPのことです。SOAPメッセージを送信するのには様々な方法が存在します。

最も基本的な方法は、URLストリームに対してprintlnすることですが、そうするためには「SOAPプロトコル」、「対象のサービスが期待し、そして送信するSOAPメッセージ」について不必要な程に多くを知る必要があります。対象とするサービスからこれらの情報を得る近道はありませんし、この方法には、あまりにも拡張性がありません。

developerWorksの前回のヒントで説明したSAAJ (SOAP with Attachments API for Java)のAPIを使用するのが、次のステップです。SAAJのAPIは、SOAPの概念をやや抽象的に操作する手段を提供しますが、URLストリームに対してprintlnするには、依然多くの問題を抱えています。

さらに使い易い方法は、SOAPメッセージ交換・プロトコルの抽象度をより向上させるアプリケーションを用意したシステムを使用することです。このシステムの利点は、以下の通りです。

  • アプリケーション・プログラマは、難解なSOAPではなく、アプリケーションのロジックに専念できます。
  • SOAP以外のメッセージ交換方式が使用可能であり、(もし仮にそうしたとしても)アプリケーションのコード変更はほんのわずかです。

JAX-RPC (Java API for XML-based RPC) のJava APIは、このような抽象的なアプローチを提供します。

JAX-RPC と Barnes & Noble のWebサービス

JAX-RPCは、SOAPに依存する代わりにWSDL (Web Services Description Language) に依存します。WSDLは、宣言的で標準化された方法でWebサービスのAPIを定義します。WSDLはサービスのためにSOAPバインディングを定義するかも知れませんが、SOAPのメッセージ交換層よりも上のレベルでサービスのより抽象的な記述をも定義します(WSDLの詳細については、参考文献を参照)。

上記で紹介したSAAJの記事の例では、Barnes & Noble (バーンズ・エンド・ノーブル: 米国の書店チェーン)のWebサービス(www.xmethods.net)を使用しました。XMethodsのサイトでは、このWebサービスのWSDLを提供しています(詳細については、参考文献を参照)。これらの例で使用されるWSDLをリスト1に示します。

リスト1. XMethodsにあるBarnes & NobleのWSDL
<?xml version="1.0" ?>
<definitions name="BNQuoteService"
    targetNamespace="http://www.xmethods.net/sd/BNQuoteService.wsdl"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:tns="http://www.xmethods.net/sd/BNQuoteService.wsdl">
  <message name="getPriceRequest">
    <part name="isbn" type="xsd:string" /> </message>
  <message name="getPriceResponse">
    <part name="return" type="xsd:float" /> </message>
  <portType name="BNQuotePortType">
    <operation name="getPrice">
      <input message="tns:getPriceRequest" name="getPrice" /> 
      <output message="tns:getPriceResponse" name="getPriceResponse" /> 
      </operation>
  </portType>
  <binding name="BNQuoteBinding" type="tns:BNQuotePortType">
    <soap:binding style="rpc"
        transport="http://schemas.xmlsoap.org/soap/http" />
    <operation name="getPrice">
      <soap:operation soapAction="" />
      <input name="getPrice">
        <soap:body
            use="encoded"
            namespace="urn:xmethods-BNPriceCheck"
            encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
      </input>
      <output name="getPriceResponse">
        <soap:body
            use="encoded"
            namespace="urn:xmethods-BNPriceCheck"
            encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
      </output>
    </operation>
  </binding>
  <service name="BNQuoteService">
    <documentation>Returns price of a book at BN.com given an
        ISBN number</documentation>
    <port name="BNQuotePort" binding="tns:BNQuoteBinding">
      <soap:address location=
          "http://services.xmethods.net:80/soap/servlet/rpcrouter" />
    </port>
  </service>
</definitions>

どのようなJAX-RPCアプリケーションでも、以下の3点を実行しなければなりません。

  1. Webサービスのクライアント側を表すServiceクラスをインスタンス化する。
  2. Webサービスのアプリケーションに対するプロキシをインスタンス化(そして場合によってはそのプロキシを設定)する。
  3. Webサービス・アプリケーションのオペレーションを呼び出す。

JAX-RPC DIIクライアント・アプリケーション

SAAJ APIは(SOAPのみに依存し)WSDLに依存しませんが、対象サービスのためのSOAPメッセージがどのようなものかを正確に知る必要があります。厳密に言えば、JAX-RPCも同様に(WDSLに含まれる情報の一部に気づいているかも知れませんが)WSDLに依存しないWebサービスにアクセスするために、DII (Dynamic Invocation Interface:動的起動インターフェース) APIを定義します。しかしながら、もうSOAPメッセージの詳細を知る必要はありません。DII JAX-RPCアプリケーションに特有な3つのステップは以下の通りです。

  1. WSDL無しのDIIServiceクラスをインスタンス化する。
  2. DIICallオブジェクトのプロキシをインスタンス化し、設定する。
  3. Callオブジェクトのinvokeメソッドを呼び出す。

WSDL無しのDII Serviceクラスをインスタンス化する

このクラスはWSDLのことを知りませんが、それでも名前が必要です。WSDLからサービスの名称を取得します。

リスト2. DII Serviceクラスをインスタンス化する
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
public class DIITip {
    public static void main(String args[]) {
        try {
            // Create a service class with no WSDL information.  You
            // still must know something about the WSDL, however: the
            // service's name.
            QName serviceName = new QName(
                    "http://www.xmethods.net/sd/BNQuoteService.wsdl",
                    "BNQuoteService");
            ServiceFactory factory = ServiceFactory.newInstance();
            Service service = factory.createService(serviceName);
        }
        catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

DII Callオブジェクトのプロキシをインスタンス化して設定する

DIIプロキシ・オブジェクトは、javax.xml.rpc.Call インターフェースを実装します。先ほど生成したserviceから、Callインターフェースのインスタンスを取得します。

リスト3. Callオブジェクトのインスタンス化
import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
public class DIITip {
    public static void main(String args[]) {
        try {
            // Create a service class with no WSDL information.  You
            // still must know something about the WSDL, however: the
            // service's name.
            QName serviceName = new QName(
                    "http://www.xmethods.net/sd/BNQuoteService.wsdl",
                    "BNQuoteService");
            ServiceFactory factory = ServiceFactory.newInstance();
            Service service = factory.createService(serviceName);
           // Now create a dynamic Call object from this service.
           // This call object is not yet associated with any
           // operation.  We'll do that below.
           Call call = service.createCall();
        }
        catch (Throwable t) {
            t.printStackTrace();
        }
    }

ここで取得したCallオブジェクトは、まだ使用可能では無く、オペレーションに関して何も知りません。以下のようなデータをCallオブジェクトに設定します。

  • オペレーション名:getPrice
  • 入力パラメータ: 文字列
  • 戻り値の型: 浮動小数点(float)
  • バインディング情報: rpc形式、エンコード方式
  • エンドポイント:http://services.xmethods.net:80/soap/servlet/rpcrouter
リスト4. Callオブジェクトに設定する
import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.ParameterMode;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import javax.xml.rpc.encoding.XMLType;
public class DIITip {
    public static void main(String args[]) {
        try {
            // Create a service class with no WSDL information.  You
            // still must know something about the WSDL, however: the
            // service's name.
            QName serviceName = new QName(
                    "http://www.xmethods.net/sd/BNQuoteService.wsdl",
                    "BNQuoteService");
            ServiceFactory factory = ServiceFactory.newInstance();
            Service service = factory.createService(serviceName);
            // Now create a dynamic Call object from this service.
            // This call object is not yet associated with any
            // operation.  We'll do that below.
            Call call = service.createCall();
          // Next, build up the information about the operation...
          // The operation name
          QName operationName = new QName(
                  "urn:xmethods-BNPriceCheck",
                  "getPrice");
                  call.setOperationName(operationName);
                  // The input parameter
                  call.addParameter(
                       "isbn",             // parameter name
                       XMLType.XSD_STRING, // parameter XML type QName
                       String.class,       // parameter Java type class
                       ParameterMode.IN);  // parameter mode
                       // The return
                       call.setReturnType(XMLType.XSD_FLOAT);
                       // The operation is an RPC-style operation.
                       call.setProperty(
            Call.OPERATION_STYLE_PROPERTY,
                       "rpc");
               // The encoding style property value comes from the
               // binding's operation's input clauses encodingStyle
               // attribute.  Note that, in this case, this call is not
               // really necessary - the value we're setting this
               // property to is the default.
               call.setProperty(
               Call.ENCODINGSTYLE_URI_PROPERTY,
                       "http://schemas.xmlsoap.org/soap/encoding/");
               // The target endpoint
               call.setTargetEndpointAddress(
               "http://services.xmethods.net:80/soap/servlet/rpcrouter");
        }
        catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

Callオブジェクトのinvokeメソッドを呼び出す

これでようやく、(ISBNから価格情報を取得する)getPriceオペレーションを呼び出すことができます。

リスト5. getPriceオペレーションを呼び出す
import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.ParameterMode;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import javax.xml.rpc.encoding.XMLType;
public class DIITip {
    public static void main(String args[]) {
        try {
            // Create a service class with no WSDL information.  You
            // still must know something about the WSDL, however: the
            // service's name.
            QName serviceName = new QName(
                    "http://www.xmethods.net/sd/BNQuoteService.wsdl",
                    "BNQuoteService");
            ServiceFactory factory = ServiceFactory.newInstance();
            Service service = factory.createService(serviceName);
            // Now create a dynamic Call object from this service.
            // This call object is not yet associated with any
            // operation.  We'll do that below.
            Call call = service.createCall();
            // Next, build up the information about the operation...
            // The operation name
            QName operationName = new QName(
                    "urn:xmethods-BNPriceCheck",
                    "getPrice");
            call.setOperationName(operationName);
            // The input parameter
            call.addParameter(
                    "isbn",             // parameter name
                    XMLType.XSD_STRING, // parameter XML type QName
                    String.class,       // parameter Java type class
                    ParameterMode.IN);  // parameter mode
            // The return
            call.setReturnType(XMLType.XSD_FLOAT);
            // The operation is an RPC-style operation.
            call.setProperty(
                    Call.OPERATION_STYLE_PROPERTY,
                    "rpc");
            // The encoding style property value comes from the
            // binding's operation's input clauses encodingStyle
            // attribute.  Note that, in this case, this call is not
            // really necessary - the value we're setting this
            // property to is the default.
            call.setProperty(
                    Call.ENCODINGSTYLE_URI_PROPERTY,
                    "http://schemas.xmlsoap.org/soap/encoding/");
            // The target endpoint
            call.setTargetEndpointAddress(
            "http://services.xmethods.net:80/soap/servlet/rpcrouter");
           // Invoke the operation
              Object[] actualArgs = {"0672324229"};
              Float price = (Float) call.invoke(actualArgs);
              System.out.println("price = " + price);
        }
        catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

java DIITipを実行すると、価格として「44.99」が取得できます。

これをSAAJの記事に掲載されたコードと比較すると、SOAPプロトコルの知識からは遠ざかっていることが分かるでしょう。必要とされる唯一のSOAP関連の情報は、オペレーション方式 と エンコード方式 だけです。

名前が示す通り、DIIのプログラミング・モデルは動的なサービスと共に動作するように設計されています。仮にサービスが多少変更された場合でも、対応するためにクライアント・コードを変更するのはそう難しくは無く、(もし、あなたが非常に堅実であれば)クライアント・コードをまったく変更しなくても良いほど動的にすることもできるでしょう。

このモデルは、まだどちらかと言えば(特にCall オブジェクトを設定しなくてはならない部分においては)複雑です。オペレーションが複雑であれば、その分Callオブジェクトに設定すべき情報も複雑になります。サービスが静的で変化しないと分かっていれば、抽象化の食物連鎖で、もう1つ上の段階に到達できるでしょう。

JAX-RPC SEI クライアント・アプリケーション

抽象化のこの次の段階では、より多くの設定が必要となりますが、その結果出来上がるクライアント・コードは遥かに簡単になります。先ずは、WSDLから始めることになります。JAX-RPC実装は、何と言ってもWSDLからJavaへのコード・ジェネレータ・・・SEI (サービス・エンドポイント・インターフェース)を生成する・・・を提供しています。これは、Webサービス・アプリケーションへのクライアント側のJavaインターフェースとなります。リスト6にXMethods の Barnes & NobleアプリケーションのSEIを示します。

リスト6. Barnes & Noble の SEI
package xmethods.bn;
public interface BNQuotePortType extends java.rmi.Remote {
    public float getPrice(java.lang.String isbn)
            throws java.rmi.RemoteException;
}

ここで WSDL-to-Java ツールを使って、Javaパッケージ(xmethods.bn)にtargetNamespacehttp://www.xmethods.net/sd/BNQuoteService.wsdl をマップしました。その結果このパッケージ内にSEIが生成されます。SEIの名称は、WSDLのportTypeのname属性(BNQuotePortType)から生成されます。getPrice メソッドは、portTypegetPriceオペレーションおよび、それが参照する message と type から生成されます。(あなたが使い慣れた)WSDL-to-Javaツールを実行した時、xmethods.bnパッケージ内に他のクラスが生成されているのを見つけるでしょう。それらを全てコンパイルすべきですが、クライアント・アプリケーションはSEIについてだけ知っていれば問題ありません。

SEIを使用してWebサービスを呼び出すには、基本的にDIIで行なったのと同様に3つのステップを行います。

  1. WSDLを使ってServiceクラスをインスタンス化する。
  2. プロキシをインスタンス化する。
  3. プロキシのオペレーションを呼び出す。

ステップ1は、SEIもDIIも類似しています。SEIのステップ2とステップ3(特にステップ2)は、簡素化されています。

WSDLを使ってServiceクラスをインスタンス化する

SEIモデルとDIIモデルにおけるこのステップでの唯一の相違点は、SEIではWSDLと言うちょっとした情報が提供されているということです。

リスト7. SEIのServiceクラスをインスタンス化する
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
public class SEITip {
    public static void main(String args[]) {
        try {
            // Create a service class with WSDL information.
            QName serviceName = new QName(
                    "http://www.xmethods.net/sd/BNQuoteService.wsdl",
                    "BNQuoteService");
            URL wsdlLocation = new URL
              ("http://www.xmethods.net/sd/2001/BNQuoteService.wsdl");
            ServiceFactory factory = ServiceFactory.newInstance();
            Service service = factory.createService(
                    wsdlLocation,
                    serviceName);
        }
        catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

プロキシをインスタンス化する

service が取得できれば、次にSEIの実装を見つけなくてはなりません。この実装は、実際のアプリケーションへのプロキシです。そのアプリケーションにどのようにアクセスすれば良いかの情報は、この実装の中に隠されています(それらは、WSDLのservice - portから入手されます)ので、一旦プロキシを入手すれば、何も設定する必要はありません(設定は既に行われています)。

リスト8. SEIの実装をインスタンス化する
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import xmethods.bn.BNQuotePortType;
public class SEITip {
    public static void main(String args[]) {
        try {
            // Create a service class with WSDL information.
            QName serviceName = new QName(
                    "http://www.xmethods.net/sd/BNQuoteService.wsdl",
                    "BNQuoteService");
            URL wsdlLocation = new URL
              ("http://www.xmethods.net/sd/2001/BNQuoteService.wsdl");
            ServiceFactory factory = ServiceFactory.newInstance();
            Service service = factory.createService(
                    wsdlLocation,
                    serviceName);
           // Get an implementation for the SEI for the given port
           QName portName = new QName("", "BNQuotePort");
           BNQuotePortType quote = (BNQuotePortType) service.getPort(
                      portName,
                      BNQuotePortType.class);
        }
        catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

プロキシのオペレーションを呼び出す

最後は、オペレーションの呼出しですが、これ以上は単純化できないでしょう。

リスト9. SEIのオペレーションを呼び出す
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import xmethods.bn.BNQuotePortType;
public class SEITip {
    public static void main(String args[]) {
        try {
            // Create a service class with WSDL information.
            QName serviceName = new QName(
                    "http://www.xmethods.net/sd/BNQuoteService.wsdl",
                    "BNQuoteService");
            URL wsdlLocation = new URL
              ("http://www.xmethods.net/sd/2001/BNQuoteService.wsdl");
            ServiceFactory factory = ServiceFactory.newInstance();
            Service service = factory.createService(
                    wsdlLocation,
                    serviceName);
            // Get an implementation for the SEI for the given port
            QName portName = new QName("", "BNQuotePort");
            BNQuotePortType quote = (BNQuotePortType) service.getPort(
                    portName,
                    BNQuotePortType.class);
           // Invoke the operation
              float price = quote.getPrice("0672324229");
              System.out.println("price = " + price);
        }
        catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

まとめ

Webサービスを呼び出す方法は幾つかありますが、その中で最も基本的なのは、SOAPメッセージを手動で生成して送信する方法です。そうするためには、対象のサービスとSOAPプロトコルについて必要以上に多くを知る必要があり、あまり実用的ではありません。一歩進めた次の方法としては、SAAJ APIを使うことです。これでもまだ、対象のサービスとSOAPプロトコルについて多くを知る必要があり、これも特に実用的とは言えません。そして、その次の段階が、JAX-RPC DIIを使用することです。これでSOAPからは遠ざかることができますが、どちらかと言えばコードはまだ複雑です。一般的に言える最良の手段は、対象サービスのWSDLからSEIを生成して、SEIのプロキシを呼び出す方法です。


ダウンロード可能なリソース


関連トピック

  • この記事で使用したソース・コードをダウンロードしてください。
  • WSDLの仕様は、Web Services Description Language (WSDL) 1.1にあります。
  • W3SchoolsのWSDL Tutorialで、WSDLを別の角度から理解できます。
  • ここに、Barnes & NobleのWebサービスに関する追加情報があります。Barnes & Noble のWSDLは、ここからダウンロード出来ます。
  • IBM WebSphere SDK for Web Services (WSDK)は、Webサービスの作成、発見、呼び出し、そしてテストのための統合キットです。この記事のリンクから、WSDK 5.0.1と多くのチュートリアルをダウンロード出来ます。
  • Java API for XML-Based RPC (JAX-RPC) Downloads & Specifications では、javadoc、クラス・ファイル、JAX-RPC 1.0自体の仕様はもちろん、SunのJAX-RPCリファレンス・インプリメンテーションへのリンクも提供しています。
  • AXISは、JAX-RPC 1.0を実装したApache Software Foundationのオープン・ソースのSOAPエンジンです。
  • developerWorksのXMLゾーンには、XMLに関する多くの記事があります。
  • Webサービスに関するより多くの情報が必要でしたら、developerWorksのWeb services ゾーンへどうぞ。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML
ArticleID=242458
ArticleTitle=ヒント: JAX-RPCでSOAPメッセージを送受信する
publish-date=09022003