[Seasar-user:1059] Re: S2Axis 1.0.0 (alpha) リリース

Taro Kato kato
2004年 10月 4日 (月) 22:52:20 JST


》小林さん

こんにちわ。グルージェント加藤です。

S2Axis1.0.0alpha,S2AxisExample1.0.0alphaを見させて頂きました。
しかし私が想定していたものと大分異なっていましたので、
まずS2Axisへの要件を整理した上で改修しました。

1.今までのAXISへの理解の基本線を変えない。普通のWebサービス
  設定に若干の変更を加えるだけで、すぐにS2対応サービスにも
  またその逆にNativeAXISサービスにも戻せるようにする。

2.AXISが実体化するWebサービスが 只のnewInstanceではなく
  S2Containerを介して得られるようにする。これがS2Axisの肝。

実際の開発現場では最初はJWSにしていても、結局WSDDに記述して
細かな制御を行うのが主流かと思います。WSDD対応は現行JOBで必須でした。


そこで私は、WSDDJavaRPCプロバイダ以外にWSDDJavaS2Providerを用意し、
インスタンシングのタイミングに、S2Containerを差し込むというアプローチを
取りました。具体的にはJavaProviderの行うmakeNewServiceObjectが生成して
いる箇所だと言うことがステップ実行で分かったので、これをoverrideして
S2Container経由で取得するようにし、別途、WSDDJavaS2Providerを用意して
WSDD上のサービスProviderに"java:S2" を指定すると S2Providerが使われる
仕組みにしました。


ユーザーが行うべきWSDDへの更新は次の通り。

純粋なAxisの場合(適用前)

 <service name="Test" provider="java:RPC">
  <parameter name="allowedMethods" value="*"/>
  <parameter name="className"
      value="org.seaser.axis.server.examples.SampleServiceImpl"/>
 </service>


S2経由への変更例(適用後)

 <service name="Test" provider="java:S2">
  <parameter name="allowedMethods" value="*"/>
  <parameter name="className"
      value="org.seaser.axis.server.examples.SampleService"/>
 </service>


このように provider属性を変更するだけです。
しかしそのままのAxisServletでは、一連の標準のproviderをロードするタイミング
が、最初のgetInstance(あるプロバイダのQName)を呼び出すタイミングと重なるため
"java:S2" の示すプロバイダーの登録が外部から行うことは出来ませんでした。
常に"Java:S2" がNotFoundになってしまいます。

このため、まずは S2AxisServlet extends AxisServletを作成することになりました。
ついでですので、SingletonS2ContainerFactoryを呼び出すS2ContainerServletの機能を
併せ持つようにしました。servletクラス名と、オプションでパラメータを追加することが
できます。configPathのapp.diconはデフォルトですのでこの場合は省略できます。

  <servlet>
    <servlet-name>AxisServlet</servlet-name>
    <display-name>Apache-Axis Servlet</display-name>
    <servlet-class>
        org.seaser.axis.server.S2AxisServlet
    </servlet-class>
    <init-param>
      <param-name>configPath</param-name>
      <param-value>app.dicon</param-value>
    </init-param>
  </servlet>


話をS2プロバイダ利用WSDDに戻します。DICONからnameで引き当てたい場合は、
以下のようにnameパラメータを追加します。

 <service name="Test" provider="java:S2">
  <parameter name="allowedMethods" value="*"/>
  <parameter name="className" value="org.seaser.axis.server.examples.SampleService"/>
  <parameter name="name" value="sample"/>
 </service>

ここで、nameがあるのに、なぜclassNameが必要かということですが、
classNameの示すクラスはWSDL開示の際にAxisがリフレクションを行うための
インターフェースとなります。S2Providerではこれに、interfaceクラスを
指定できます。WSDL開示の際にはインスタンシングされません。
インスタンシングされるのは実際のメソッドをクライアントから呼び出した
際に行われます。このインスタンシングの際に今度はS2Containerが必要とする
のはnameとclassNameのどちらであるかが選択されます。name属性があればnameから、
無ければclassNameの示すclassで引き当ててコンポーネントを取得します。

classNameにインターフェースを指定することで、allowedMethodsに
細かいメソッド名を指定せずとも、interfaceの持つメソッドだけが
Webサービスとして公開できるというメリットがあります。
TYPOも防げるし自分では気に入ってます。


次にJWSのS2対応を考えました。

JWSのS2版についてもインスタンシングのタイミングをとらえることが
重要です。Axisを追った結果、インスタンシングする根っこはWSDDの
場合と同様に、JavaProviderでした(実際にはそのまま継承している
RPCProvider)。JWSHandlerを良く見るとRPCProviderのインスタンスを
作成している箇所があります。
ここでセットしたプロバイダが、インスタンシングの際に使われるはずだ!
とピーンと来ました。
そこで、JWSHandlerを継承したS2JWSHandlerで、new RPCProvider()を、
new S2Provider() にしたところ、読みどおり、S2Providerのインスタンス生成
に入り込んできました。
ただ難点が1つあって、new RPCProvider() の前後が分離されていないので
当初はコードをまるっきりコピーしないとできないというトホホな状況でした。

これで行けたと思いきや、JWSをインターフェースクラスにして、実装クラスを
別でJavaで用意していたのですが、インターフェースクラスに対応したコンポーネントが
DICONから見つからないと怒られます。
この理屈が分かるのにしばらく苦労しました。

まず、JWSの場合は独自のクラスローダーを使い動的にコンパイルしたものを
別の場所(jwsClasses)に配置します。JWSをinterfaceとした場合、implementsするためには
コンパイル時にinterfaceのコピーが必要になります。このためデフォルトパッケージに
コピーを配置してコンパイルすることになります。JWSと全く同じソースをビルドした
クラスが、classes/ に置かれるわけですが、クラス名は一緒であるにも関わらず、
jwsClasses/ のものとは異なるものであるとVMは認識します。

別のアドレス空間にロードされるため、例えば EchoHeadersIntf.jws を実装した
EchoHeadersImpl.java のコンパイル結果は、コンパイル時に必要なEchoHeasersIntf.javaを
implementsしているのであって、EchoHeadersIntf.jwsのコンパイル結果をimplementsしてい
ないものと判断し、「実装しているクラスは無い」という実行時にエラーとなります。

これは結論から言えば、私のやろうということは不可能を意味していることを示していました。
不可能は言い過ぎとしても少なくともバイトコードエンジニアリングなどの高度な対応が必要でしょう。

JWSはコンパイルの際に環境化の全てのClassPathを使って動的にコンパイルしますが、逆に
JWSのclassへの参照を開発時に提供することが誰もできない完全に独立した構成になっています。

仕方ないのと私個人はJWS対応はS2には不要といったんは位置づけたのですが、
そもそもS2に於けるJWSの必要性は何かをもう一度よく考えることにしました。


利点として良く言われるのはWSDDに1つ1つのサービス定義を記述しなくても
「置くだけで良い」ということです。しかし今回のようにJWSのクラスを継承したり実装したり
という目的のものは作成できません。それに、置くだけで良いと言いながらも、
結局細かなデバッグが困難なため、大抵はただのラッパーになってしまいがちです。

他に利点が見いだせにくかったので、逆説として「置くだけじゃない構成は何が不満か」を
考えてみました。
そう考えればWSDDに対する <service>定義は冗長です。1つ2つぐらいじゃ何でも無いですが、
登録するのにコマンドツールがあるからといっても全く解決の答えにはなっていないのは
使ってみると分かります。いくつもサービスを公開する時は沢山書かなければなりませんし、
生き死にをJWSファイルというJavaファイルを配置・削除するだけでコントロールできないのも
手間ということかもしれません。
#個人的には<service>タグで事足りているので、かなり無理に必要性を導いていますが。


そこで私は完全に割り切ってJWSという物理ファイルを用いないS2ならではの
S2JWSを考えました。

まず、クラスロードで不整合が起きないよう、そもそも動的コンパイルする
JWSファイルというものはコンテントルートに置きません。
実際に存在するクラス(デフォルトのクラスローダに登録されるリンクのとれているもの)
で構成することで、開発時のデバッグもテストも容易に行えることにもなります。
S2JWSを使うには、WSDDの<deployment/globalConfiguration/requestFlow>の中で
以下のように定義を記述します。

   <handler type="java:org.seaser.axis.server.S2JWSHandler">
    <parameter name="package" value="org.seaser.axis.server.examples.services"/>
   </handler>

そもそもAxisは拡張子(extensionパラメータ)によってハンドラを設けられるような素振りを
見せながら、AxisServletのdoGetでisJWSの判定が直接 ".jws" リテラルと比較していて
トホホな状態です。仕方ないので、少々オーバーヘッドが発生しますが、まずS2Containerに
存在するかを確認して存在すればそのコンポーネントを、存在しなければ元々のAxisのJWS
処理に任せるように作成しました。これには S2JWSHandlerを作成してJWSHandlerをoverride
しています。

packageパラメータは、指定パッケージ内に存在する全てのクラスがWebServiceの対象と
なるということを意味します。packageが指定されていなければこれまでと同様、
デフォルトパッケージ内のものが対象となります。

これで、いくつサービスクラスがあっても全部記述しなくて良いという利点(?)が
得られます。

ビジネスロジックをサービスメソッドにガシガシ書いておいて、後から必要に応じて
DICONに書いてトランザクションで挟み込んだりすると良いでしょう。
S2JWSの場合は nameでDICONから引き当てられずclassだけですのでご注意を。


通常の<service>に記述した場合、クラスまたはnameで必ずDICONから引き当てられないと
いけません。
#ただしcanPassToAxis というサーブレットパラメータにtrueを設定すると
#Componentが存在しない場合には Axisのデフォルト処理に任せられます。
#nameパラメータを明示している場合はダメですが。
    <init-param>
      <param-name>canPassToAxis</param-name>
      <param-value>true</param-value>
    </init-param>

S2JWSの場合はデフォルトでcanPassToAxis=trueと同じ動作をします。
(jwsのように微妙に柔軟性を持たせました)

開発中に突然「あ!このサービスのメソッドはトランザクションで挟み込まないと!」
となったら、必要に応じてDICON上でインターセプトしたり、
「Axisは例外をクライアントに丸投げするからサーバーに例外ログを残したいな」と
思ったら、それなりのインターセプタを作成してpointcutしたりすれば良いかなと
思います。それまではDICONに記述しないなりの普通の動作をします。


…と長文になりましたが、そんな感じの S2Axis を作成しました。

以上、土日に作って、後輩にS2JWS対応をデバッグしてもらったものを
以下にアップしています。

http://package.gluegent.com/~kato/s2axis_beta.zip

参照していただいて、せっかくなので、できれば、正規のS2Axisに
入れてもらえるとみんな幸せになれると思います。

ただ、今までのS2Providerは使えなくなります。あらかじめセットされたComponentDefに
依存するのではなくProviderに渡るclassNameからインスタンスを導き出すためです。
すみません。

よろしくお願いします。


[[[[[[[[[[[[[[[[[[[[[[ G l u e g e n t , I n c .
[[[[[[[[[[     [[[[[[[
[[[[[[      [[[[[[[[[[
[[[[      [[[[[[[[[[[[    http://www.gluegent.com/
[[      [[[[[[    [[[[
[[[         [[[[   [[[ System Development Division
[[[[[[[[[[[[[[   [[[[[
[[[[[[[[[[[[   [[[[[[[    T a r o  K a t o
[[[[[[[[[[  [[[[[[[[[[




Seasar-user メーリングリストの案内