top / index / prev / next / target / source

2005-08-01 diary: Java: Apache Axis: クラスの配列を送受信したい → WSDLからWebサービスを作成

いがぴょんの日記 日記形式でつづる いがぴょんコラム ウェブページです。

old-v2

Java: Apache Axis: クラスの配列を送受信したい → WSDLからWebサービスを作成

Javaクラスの配列を転送する接続性の試験を行うため、WSDLからWebサービスを作成するパスを試しました。 → Apache Axis 1.2.1のメモリ消費量が 私には許容しがたいサイズであることが判明しました…

Java: Apache Axis: クラスの配列を転送したい → WSDLからWebサービスを作成

クラスの配列を転送したいので、WSDLからWebサービスを作成するパスを試しました。なお、なぜコレクション型は用いるべきではないのかについては、下記に記載があります。

チェックを行う上で利用した組み合わせは下記になります。

まず 先に WSDLを作成します。(これが私には難しいです…)

WSDLを入力として、WSDL2Javaを実行します。 java -classpath axis.jar;commons-logging-1.0.4.jar;commons-discovery-0.2.jar;jaxrpc.jar;saaj.jar;wsdl4j-1.5.1.jar org.apache.axis.wsdl.WSDL2Java --server-side --skeletonDeploy true KantanHello.wsdl ※スケルトン生成モードで実行しています。 *SoapBindingImpl.java というファイルが作成されるので、そこに具体的な処理を記載していきます。※こちら側がサーバ処理となります。 XXXXSoapBindingImpl.java (抜粋)

public class XXXXSoapBindingImpl implements
        localhost.axis.XXXX.KantanHello {
    public DefaultNamespace.ResponseRecord hello(
            DefaultNamespace.RequestRecord request)
            throws java.rmi.RemoteException {
        try {
            int size = request.getFieldR2();
            ResponseRecord response = new ResponseRecord();
            response.setField1("もどりレスポンス");
            response.setField2(123);
            response.setBody(new BodyRecord[size]);
            for (int bodyIndex = 0; bodyIndex < request.getFieldR2(); bodyIndex++) {
                BodyRecord body = new BodyRecord();
                response.getBody()[bodyIndex] = body;
                body.setField21("もどりボディ");
                body.setField22(456);

                body.setDetail(new DetailRecord[size]);
                for (int detailIndex = 0; detailIndex < size; detailIndex++) {
                    DetailRecord detail = new DetailRecord();
                    body.getDetail()[detailIndex] = detail;
                    detail.setField31(request.getBody()[0].getDetail()[0].getField31());
                    detail.setField32(456);
                }
            }
            return response;
        } catch (Exception ex) {
            System.out.println(ex.toString());
            ex.printStackTrace();
            return new ResponseRecord();
        }
    }
}

※便宜上 null値が無いという前提のもとに全てのフィールドに値をセットしています。 Axisへのデプロイの仕方。

配布するためのライブラリ (jarファイル) を作成します。 build.xml

<?xml version="1.0" encoding="UTF-8" ?><project name="sample" default="build" basedir=".">

        <target name="jar">
                <mkdir dir="bin" />
                <delete file="test.jar" />

                <zip destfile="test.jar">
                        <zipfileset dir="bin" includes="**/*.class" />
                </zip>
        </target>

        <target name="build" depends="jar">
        </target></project>

※このファイルを libフォルダなどクラスパス上に配置する。 Axisに wsddファイルをデプロイを行います。(jarファイルは単に手でコピーしました) java -classpath bin;lib/axis.jar;lib/jaxrpc.jar;lib/commons-logging-1.0.4.jar;lib/commons-discovery-0.2.jar;lib/saaj.jar;lib/wsdl4j-1.5.1.jar org.apache.axis.client.AdminClient deploy.wsdd ※Axisサーバが起動している状態で上記コマンドを実行します。 この正式な方法だとURLが JWSとは変わるようです。この点に注意が必要です。 http://localhost:8080/axis/services/KantanHello ※変更後URLの例 呼び出しプログラムの例※こちら側がクライアント処理となります。 TestCaller.java

import java.rmi.RemoteException;

import javax.xml.rpc.ServiceException;

import localhost.axis.KantanHello_jws.KantanHello;
import localhost.axis.KantanHello_jws.KantanHelloServiceLocator;
import DefaultNamespace.BodyRecord;
import DefaultNamespace.DetailRecord;
import DefaultNamespace.RequestRecord;
import DefaultNamespace.ResponseRecord;

public class TestCaller {
    private static final int COUNT = 100;

    private static final int RECORD_SIZE = 10;

    private static void callMethod(boolean isSysout) throws ServiceException,
            RemoteException {
        KantanHello hello = new KantanHelloServiceLocator().getKantanHello();
        ResponseRecord response = hello.hello(getRequestRecord());
        if (isSysout) {
            System.out.println(response.getField1());
        }
    }

    private static RequestRecord getRequestRecord() {
        RequestRecord record = new RequestRecord();
        record.setFieldR1("リクエスト");
        record.setFieldR2(RECORD_SIZE);

        record.setBody(new BodyRecord[RECORD_SIZE]);
        for (int bodyIndex = 0; bodyIndex < RECORD_SIZE; bodyIndex++) {
            BodyRecord bodyRecord = new BodyRecord();
            record.getBody()[bodyIndex] = bodyRecord;
            bodyRecord.setField21("ボディ");
            bodyRecord.setField22(123);
            bodyRecord.setDetail(new DetailRecord[RECORD_SIZE]);
            for (int detailIndex = 0; detailIndex < RECORD_SIZE; detailIndex++) {
                DetailRecord detailRecord = new DetailRecord();
                bodyRecord.getDetail()[detailIndex] = detailRecord;
                detailRecord.setField31("でぃている");
                detailRecord.setField32(543);
            }
        }
        return record;
    }

    public static void main(String[] args) {
        try {
            long start = System.currentTimeMillis();
            callMethod(true);
            long end = System.currentTimeMillis();
            System.out.println("初回呼出の所用時間:" + (end - start) + "ミリ秒");

            KantanHello hello = new KantanHelloServiceLocator()
                    .getKantanHello();

            start = System.currentTimeMillis();
            for (int index = 0; index < COUNT; index++) {
                ResponseRecord response = hello.hello(getRequestRecord());
            }
            end = System.currentTimeMillis();
            System.out.println("" + COUNT + "件処理するための所用時間:" + (end - start)
                    + "ミリ秒");
            System.out.println("1件あたり:" + (end - start) / COUNT + "ミリ秒");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

呼び出しの時間計測結果 (インスタンス生成省略バージョン) 呼出回数1件あたり処理時間1 40ミリ秒 10 25ミリ秒 100 22ミリ秒 1000 16ミリ秒 ※レコードサイズ = 1 で測定呼出回数1件あたり処理時間1 100ミリ秒 10 89ミリ秒 100 55ミリ秒 1000 45ミリ秒 ※レコードサイズ = 5 で測定 →総計25件のデータの送受信となる呼出回数1件あたり処理時間1 340ミリ秒 10 192ミリ秒 100 146ミリ秒 1000 135ミリ秒 ※レコードサイズ = 10 で測定 →総計100件のデータの送受信となる呼出回数1件あたり処理時間1 4276ミリ秒 10 3998ミリ秒 100 3883ミリ秒 ※レコードサイズ = 50 で測定 →総計2500件のデータの送受信となる呼出回数1件あたり処理時間1 計測不能 (OutOfMemoryErrorエラーが発生してしまい、計測できません) org.xml.sax.SAXParseException: XML document structures must start and end within the same entity. が発生します。 →速度比較だけに終始するはずだったのですが、10000件程度でメモリ不足エラーが発生してしまうことが判明しました。 →つきつめてメモリ消費量を調べる時間はないので そのあたりは割愛します。  JavaVMのデフォルト値で動作させました。タスクマネージャ上では 96MB程度のあたりのように見えました。 ※レコードサイズ = 100 で測定 →総計10000件のデータを想定していた。 Axis 1.2.1 の実装上での弱点がはっきりと分かりました。処理のうえでのメモリ効率が極端に悪すぎるのです。その点をさして 多くの方々が「重い」と言われていたのですね…。

この結果を受けて、現時点での Apache Axis の採用を完全に断念しました (T_T) Javaにおいて SOAPが普及するには、根っこのところの実装の実行速度に関するブレークスルーが必要なのでしょうね…。この事実を知り、結構ショックを受けました。

…ここで我慢をしないと、、、我慢がゆるいと オープンソースのSOAP実装とかを作り始めそうな自分が未来に存在するのが はっきりと認識できます。フルでSOAP APIをサポートしなくても良いから、さくさくっと動作する ミニSOAP実装を だれかオープンソースで作ればよいだけなのですけれどもね…

注:既にオープンソースのSOAP実装がいくつも世に存在していることを知っています。しかし 例えば ActiveSOAPのホームページを見たのですが、いまいち私の趣味とは異なる雰囲気の仕様だったのです。

2005.10.31追記 読者の方からのツッコミ

Subject: Apache Axisでの配列の送受信と時間・メモリの問題について伊賀様はじめまして、畠山と申します。よく日記を拝見させて頂いております。

2005/08/01の日記「Java:Apache Axis:クラスの配列を送受信したい・・・」を拝見し、メールをお送りすることにしました。

私も業務でApache Axis(1.2)を利用しているのですが、上記日記と同様の問題に遭遇し、困っています。私自身はまだ試していないのですが、以下のページに記述のある「SAXストリーミング」を用いることで、解決できないものかと考えています。

標準ではONになっていないオプションなので、業務上利用してよいものかは、微妙かと考えているのですが...

そもそも、現在リリースされているAxis 1.2.1や1.3でサポートされているかさえ未調査です。曖昧な情報ばかりで申し訳ありません。また、見当違いな内容を書いていた場合も、申し訳ありません。

伊賀さんにとって、何かお役に立てば幸いです。今後も楽しく拝見させて頂きたいと思います。

ここからいがぴょん貴重な情報、ありがとうございます。私も このオプションについて調査してみます。

関連する日記

軽量SOAPは 実は非常に必要とされるのかしらん…

仕様を極めて限定して実装された SOAPは、じつは非常に必要とされているのかも知れません。ふと「うそSOAP (USOAP)」 という作業を昔行ったことを思い出しました。

ほんとうに限定的な仕様をもって 軽量SOAPを実装すれば、じつはかなり嬉しいのかも知れません。「POJOベースの軽量SOAP」って言うと かっこよく聞こえそうです。(StAXではなくって、あくまでもPOJOベースかもっ)ここで難易度の高い点は、いかに仕様を限定しきって実装を高速化するのか、というさじ加減だと思います。例えば、WSDLを食う機能は省略して、サーバ上のJavaのクラスを入力に SOAPのサーバ・クライアントのスタブおよびWSDLを自動生成、などが考えられます。 …逆かしら、、、WSDLを食う方が実装は楽かも知れませんね。むむむっ、間を取って、なにか入力の出力として、WSDLおよび Javaソースコードを自動生成かしら…。その選択が軽量化のためのひとつの重要なポイントになりそうですね。

リッチクライアントという観点からは、SOAPサーバ側の実行効率をまずは適正化するだけ、それだけで かなりの前進が得られるように考えます。

…ああ、あぶない、あぶない。自分で SOAPの実装を行う気持ちになりかかっていました (苦笑)

2005.08.02後日談 やっぱり我慢せずに実装したほうが良いような気がしてきました。とりあえずモックアップを試作して情報収集中です。

左の腰と右の股関節が痛い…

昨日、帰路にて子供が寝入ってしまったので、だっこして家まで帰りました。これが原因してか、腰と股関節が ちょっと痛いです。

世間のニュースから


この日記について