S2Axis 開発記 プロトタイプその3 動的起動クライアント

入門ばっかりやってないで開発もしろ!! という声が聞こえた気がするのでちょっとだけ準備を進めてみる.
サーバ側はプロトタイプが動いているので,次はクライアントですね.
依頼主であるはぶさんとこではクライアントは .NET らしいのですが,一応 S2Axis としては両方揃っていないとね♪


そのクライアントサイドですが,プログラミングスタイルとしては二通りあるようです.
一つは任意のサービスの任意のメソッドを自由に呼び出す方法.CORBA の DII (Dynamic Invocation Interface) に相当するものですね.これを動的起動と呼びましょう.
もう一つは WSDL から生成したスタブ (Proxy) を使う方法.こちらは静的起動と呼びましょう.
Amazon みたいな外部の Web サービスを利用する場合は静的起動を使うことが多いでしょうか.一方,システム内部のサービスを利用する場合は動的起動の方が多いかも.ま,いずれはどっちも揃えないとね♪
Axis のドキュメントでは,最初に動的起動から説明されています.そこで,まずは動的起動から学習します.


Axis ユーザガイド」には,とても簡単なサンプルが掲載されています.

public class TestClient
{
   public static void main(String [] args) {
       try {
           String endpoint = 
                    "http://nagoya.apache.org:5049/axis/services/echo";
     
           Service  service = new Service();
           Call     call    = (Call) service.createCall();

           call.setTargetEndpointAddress( new java.net.URL(endpoint) );
           call.setOperationName(new QName("http://soapinterop.org/", "echoString") );

           String ret = (String) call.invoke( new Object[] { "Hello!" } );

           System.out.println("Sent 'Hello!', got '" + ret + "'");
       } catch (Exception e) {
           System.err.println(e.toString());
       }
   }
}

これだけで Web サービスを呼び出せちゃうんですね.
キーになるのは

  • Call

というクラス.
こいつに呼び出したい Web サービスのエンドポイント (URL) およびメソッド名を設定して,

    • invoke(Object[])

を呼び出せばいいだけみたい.
後,Web サービスによってはレスポンスメッセージに戻り値の型を設定してくれない場合があるらしいので,

    • setReturnClass(Class)

で希望の戻り値の型を指定した方がよさげだとか.
マジでこれだけ? 楽勝かもぉ〜.


これ,S2Dao みたいに Javainterface に対する Interceptor を作るとよさげ.
使い方のイメージは

<component class="SomeInterface">
    <aspect>
        <component class="org.seasar.axis.client.DynamicInvocationInterceptor">
            <arg>service</arg>
            <arg>"http://〜"</arg>
        </component>
    </aspect>
</component>

みたいな.


で,Interceptor ではメソッド名と引数が取れるから,それを Call に渡してあげれば OK のはず.
クライアントサイドは実装クラスの直接呼び出しとWeb サービスの呼び出しを .dicon ファイルだけで切り替えられます.


本当は準備編として書き始めたのですが,すぐにでも出来そうなのでプロトタイプ編に変更!!
...
うーみゅ,意外と躓いたなぁ.
なんかですね,サンプル通りだとコンパイル通らないよ?
Service#createCall() が返すのは Axis の Call じゃなくて,JAX-RPCCall なんですけど...
JAX-RPCCall には,setReturnCalass(Class) がないんですけど...
どうやら,Axis の CallJAX-RPCCall にキャストできるようなので,それで凌ぎました.
というわけで,Interceptor はこんな感じ.

package org.seasar.axis.client;
import java.lang.reflect.Method;
import java.net.URL;
import javax.xml.namespace.QName;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
import org.seasar.framework.aop.interceptors.AbstractInterceptor;

public class DynamicInvocationInterceptor extends AbstractInterceptor {
    final private Service service;
    final private URL url;

    public DynamicInvocationInterceptor(final Service service, final URL url) {
        this.service = service;
        this.url = url;
    }

    public Object invoke(final MethodInvocation invocation) throws Throwable {
        final Method method = invocation.getMethod();
        final Call call = (Call) service.createCall();
        call.setTargetEndpointAddress(url);
        call.setOperationName(new QName("http://soapinterop.org/", method
                .getName()));
        call.setReturnClass(method.getReturnType());
        return call.invoke(invocation.getArguments());
    }
}

ということで,お試し.
まずはサービスのインタフェース.

package foo;

public interface Hello {
    String say();
}

サービスとか格好良く呼んだところで所詮この程度.(^^;
その実装クラス.

package foo;

public class HelloImpl implements Hello {
    private String message;
    public HelloImpl(String message) {
        this.message = message;
    }
    public String say() {
        return message;
    }
}

そしてクライアントの実行用クラス.

package foo;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;

public class Client {
    public static void main(String[] args) {
        SingletonS2ContainerFactory.init();
        S2Container container = SingletonS2ContainerFactory.getContainer();

        Hello hello = (Hello) container.getComponent(Hello.class);
        System.out.println(hello.say());
    }
}

そして app.dicon.まずはローカルで動かしてみます.

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
	"http://www.seasar.org/dtd/components.dtd"
>
<components>
    <component name="hello" class="foo.HelloImpl">
        <arg>"Hello, World"</arg>
    </component>
</components>

こいつを実行!!

Hello, World

まぁ,当たり前に動きました.
次に,この app.dicon をサーバに配置します.
そして,クライアントの app.dicon を次のように変更します.

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
    "http://www.seasar.org/dtd/components.dtd"
>
<components>
    <component name="service" class="org.apache.axis.client.Service"/>

    <component class="foo.Hello">
        <aspect>
            <component class="org.seasar.axis.client.DynamicInvocationInterceptor">
                <arg>service</arg>
                <arg>new java.net.URL("http://localhost:8080/axis/services/hello")</arg>
            </component>
        </aspect>
    </component>
</components>

そしてクライアント用のクラスを実行!!!!

Hello, World

\(^o^)/