ファイヤープロジェクト
JWSでWebサービス構築(AXIS 1.2)
2005-06-12T21:00+09:00   matsu
やはり何事も動くものが無くては楽しめない,ということで,JWSを使用して簡単なWebサービスを構築し,それにリクエストするクライアントを作成してみた.
JWS(Java Web Service)は,.javaファイルを.jwsファイルに(拡張子を)変更したものである. JSPで簡単にサーブレットを作成できるのと同じような感覚で,JWSで簡単にWebサービスを作成できる. すなわち,Servlet ContextにJWSファイルを置いて,あとはクライアントからアクセスするだけである. wsdd(Web Service Deployment Descliptor)を記述することなくWebサービスを提供できる. JWSは以下のような制限はあるものの,簡単にWebサービスが提供できるので,単純なWebサービスや,開発用,プロトタイプなどに便利である.
パッケージ指定できない
JWSは自動的にJAVAソースファイルとなりコンパイルされてCLASSファイルとなる. すなわち通常のJAVAプログラミングとほぼ同様に作成できるのだが,そのクラスを任意のパッケージの下に納めることはできない.
SOAP MSG(メッセージング)に対応できない
JWSを使用するWebサービスでは,SOAP電文のシリアライズ,デシリアライズはAXISで自動的に行われる. これは簡単にWebサービスを提供できる理由なわけだが,逆にこれが制限でもある. すなわち自分で作成したクラスのメソッドがAXISに呼ばれるだけという仕組みになる(SOAP RPC). SOAP電文も取得できないので,SOAP電文の読み込みや,作成,編集したものを回答することができない(SOAP MSG).
JWSを使用するサンプルを作成してみた. サンプルのビルド,実行方法は以下(※).
  1. 展開する.
    $ tar zxvf HelloAxis-0_01.tar.gz 
    
  2. ビルドのための準備をする.
    $ ant init
    
  3. 依存ライブラリのダウンロード
    $ ant preparelib
    
  4. SUNのサイトから,activation.jarを含むjaf-1_0_2-upd2.zipを入手する. atcivation.jarはlibの下に置く.
  5. warファイルのビルド
    $ cp conf/rel_filter.properties filter.properties
    $ ant war
    
  6. axsisample.warをサーブレットコンテキストに置く.
  7. クライアント起動.
    $ java -jar axissample.jar jwsファイルのURL 呼び出しメソッド [メソッドパラメータ]
    
呼び出しメソッドは以下から選択する.
hello
"hello!!!"という文字列が回答される.
echo
メソッドパラメータに指定した文字列が回答される.
countup
メソッドパラメータに指定した数字が,Webサービス側でカウントされ,カウントの合計値が回答される.
hisotry
呼び出したメソッドの履歴が回答される. メソッドパラメータにtrueを指定すると,同時に履歴がクリアされる. メソッドパラメータを指定しないか,falseを指定すると,履歴はクリアされない.
実行例を以下に示す.
$ java -jar axissample.jar http://soapserver/fireproject/HelloAxis.jws hello
ret = hello!!!

$ java -jar axissample.jar http://soapserver/fireproject/HelloAxis.jws echo HOGE
ret = HOGE

$ java -jar axissample.jar http://soapserver/fireproject/HelloAxis.jws countup 1
ret = 1

$ java -jar axissample.jar http://soapserver/fireproject/HelloAxis.jws countup 2
ret = 3

$ java -jar axissample.jar http://soapserver/fireproject/HelloAxis.jws history
ret[0] = hello
ret[1] = echo
ret[2] = countup
ret[3] = countup
ret[4] = history

$ java -jar axissample.jar http://soapserver/fireproject/HelloAxis.jws history true
ret[0] = hello
ret[1] = echo
ret[2] = countup
ret[3] = countup
ret[4] = history
ret[5] = history

$ java -jar axissample.jar http://soapserver/fireproject/HelloAxis.jws history
ret[0] = history
実際には毎回警告が出力されるが,今は関係ない内容なので,省略した.
※ 3番目の手順にて各種ファイルがダウンロードされるが,これらはファイヤープロジェクトの配布物ではなく,それぞれにライセンスがある. ダウンロード下ものはdownloadディレクトリ下に配置されるので,それぞれ確認されたい.
サンプルのJWSを以下に示す.
このようにJWSは何の変哲もないJAVAソースファイルに見える. ただし,以下の点に注意する.
  • パッケージ指定しない
  • 全publicメソッドがWebサービスとして公開されることになる
  • 1要求毎にnewされる
  • Webサービスメソッドの引数や返り値は,SOAPエンコードできるものでなければならない(使用できる型に制限がある).
最後の点は,SOAPやAXISを理解する上で重要なので,後で細かく記述する. サンプルのクライアントのソースを以下に示す.
サンプルのようにSOAP RPCクライアントの基本的な処理順序は以下のようになっている.
Serviceオブジェクトのnew
欲しいのはCallオブジェクトであるが,まずそのFactoryであるServiceオブジェクトを作成する.
Service service = new Service();
このサンプルでは大した意味はないが,Serviceオブジェクトはサービスの起点を表現し,普通WSDLファイルをもとに生成されるものらしい.
Callオブジェクトの取得
作成したServiceオブジェクトから,Callオブジェクトを取得する.
Call call = (Call)service.createCall();
Callでキャストしている. 一見これは不要に見えるが,いつは左辺のCallとServiceオブジェクトのcreateCallメソッドの返り値のCallはパッケージが異なる. サンプルではキャストしないと以下のエラーとなる.
互換性のない型
検出値 : javax.xml.rpc.Call
期待値 : org.apache.axis.client.Call
         Call call = service.createCall();
サービスのエンドポイントを指定する.
このサンプルでは,コマンドの第一パラメータで指定する,JWSファイルのURLである.
call.setTargetEndpointAddress(new java.net.URL(endpoint));
呼び出しメソッドを指定する.
呼び出しメソッドは,名前空間付きのメソッド名である.
call.setOperationName(method);
実行する.
メソッド名がhelloの場合は,以下のようにしている.
param = new Object[0];
String ret = (String)call.invoke(param);
パラメータをObject配列として渡す. 引数がないメソッド呼び出しの場合は,上のように要素数0にしておけばよい. また,呼び出しメソッドhelloは,JWS側ではStringを返すので,Stringキャストする.
サンプルでは,引数のあるパターン,ないパターン,返り値が配列のパターンがそれぞれある. また,historyメソッドはオーバーロードなっており,これも適切に処理されることを確認できた.
ここで少しSOAP電文の基本的事柄について記述しておく.
  • SOAP電文は,XML文書である.
  • 一つのSOAP電文は,一つのSOAPエンベロープ(ルート要素soapenv:Envelop)に収まっている.
  • 一つのSOAPエンベロープには0個以上のSOAPヘッダ(要素soapenv:Header)がある. SOAPヘッダにはターゲットのWebサービスのコンテキストとなるような情報を記述する. 例えば,Webサービスの中継ノードが処理をするための情報や,セッション情報などを(あれば)記述する.
  • 一つのSOAPエンベロープには一つのSOAPボディ(要素soapenv:Body)がある. SOAPボディには,サービスリクエストの内容や,その回答内容を記述する.
では,サンプルで何が行われたのかを詳しく確認してみる. まず,記述したコードは以下.
  • JWSでいくつかのメソッドを記述した. ここで,あとでポイントとなるのは,JWSを配置したURLと,メソッド名,引数,返り値の型.
  • クライアントでSOAP RPC呼び出しを記述した. ここで,先のJWSの配置URLと,メソッド名,引数,返り値の型の整合性を取るように注意した.
実際,裏のAXISが生成するSOAP要求電文では,WebサービスURL,呼び出しメソッド名,引数が,そしてSOAP回答電文には返り値の型が記述されている. 以下は,サンプルのhistoryメソッドを引数付きで呼び出さした際の電文をTCPMonで採取したものである.
SOAP要求電文のボディ部にて,指定したメソッド名のタグがあり,その子要素として引数が指定されている.
<soapenv:Body>
   <history
       soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <arg0 href="#id0"/>
   </history>
   <multiRef id="id0" soapenc:root="0"
       soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
       xsi:type="soapenc:boolean"
       xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">true</multiRef>
</soapenv:Body>
引数の型は要素multiRefの属性xsi:typeによって指定され,その値は要素multiRefの内容に記述されている. SOAP回答電文のボディ部には,指定されたメソッド名+"Response"のタグがあり,その子要素メソッド名+"Return"に返り値が記述されている.
<soapenv:Body>
   <historyResponse
       soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <historyReturn soapenc:arrayType="xsd:string[7]"
          xsi:type="soapenc:Array"
          xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
         <historyReturn xsi:type="xsd:string">hello</historyReturn>
         <historyReturn xsi:type="xsd:string">countup</historyReturn>
         <historyReturn xsi:type="xsd:string">countup</historyReturn>
         <historyReturn xsi:type="xsd:string">hello</historyReturn>
         <historyReturn xsi:type="xsd:string">countup</historyReturn>
         <historyReturn xsi:type="xsd:string">countup</historyReturn>
         <historyReturn xsi:type="xsd:string">history</historyReturn>
      </historyReturn>
   </historyResponse>
</soapenv:Body>
やはり要素historyReturnの属性xsi:typeや属性soapenc:arrayTypeなどで返り値の型が記述されている. さらに今回の返り値は配列なので,配列の各要素についても型と値が記述されている. このようにして,サンプルのようなわずかなコーディングで,SOAP RPCが実現されている.
前節の内容から,AXISはSOAP RPCに必要な,メソッドの名前,引数や返り値の型に関する情報を自動的に記述してくれていることが分かった. これにより,SOAP RPCの上位では煩わしいSOAP電文のシリアライズ,デシリアライズ処理を記述する必要がなくなっている. さて,Java言語のいろいろな型がSOAP電文に記述され,メソッド実行のために使用されている. すなわちJava言語の型に対してSOAP電文ではどう記述するか(=SOAPエンコーディング)について,クライアントとサーバで合意を取っておく必要がある. サンプルでは,単純な型のものは,XMLスキーマ上の表記方法を使用して,xsi:type属性とxsd:で始まるその値によって表現している. より複雑な型であるStringの配列は属性soapenv:encodingStyleによってエンコーディングスタイルを指定している. 独自に定義したクラスのオブジェクトを返すようなパターンでは,シリアライズとデシリアライズを設定などする必要があるようだが,これに関しては別途記述する. このエンコードスタイル指定は,この属性をもつ要素とその子要素に適用される. で,要素historyReturnの属性soapenc:arrayTypeの値や,属性xsi:typeの値に先の属性soapenv:encodingStyleで指定したエンコーディングスタイルを使用している.
JWSファイルはサーバ上でアクセスされるとコンパイルされる. コンパイルが通るかどうかレベルの確認を,ローカルで確認して,コンパイルできればデプロイという作業手順にすると,作業効率が上がるかもしれない. そこで,サンプルでは,以下のようなantタスクを作成した.
<!-- JWSコンパイルテスト -->
<target name="buildjwstest"
        description="Build JWS test.">
  <mkdir dir="${jws.tmp.dir}" />
  <mkdir dir="${jws.class.dir}" />
  <native2ascii src="${src.jws}" dest="${jws.tmp.dir}"
	  includes="**/*.jws"
	  ext=".java" encoding="EUC_JP"/>	
  <javac srcdir="${jws.tmp.dir}" destdir="${jws.class.dir}">
    <classpath>
      <fileset dir="lib">
        <include name="**/*.jar" />
      </fileset>
    </classpath>
  </javac>
  <delete verbose="true">
    <fileset dir="${jws.tmp.dir}" />
    <fileset dir="${jws.class.dir}" />
  </delete>
</target>
これで
$ant buildjwstest
などとすると,JWSファイルを一旦JAVAファイルとしてコピーし,それをコンパイルする. これでデプロイ前にJWSファイルがコンパイル可能かどうかの確認ができる.
matsu(C)
Since 2002
Mail to matsu