ファイヤープロジェクト
ハンドラとハンドラチェイン(AXIS 1.3)
2005-10-31T02:05+09:00   matsu
AXISは,ハンドラとハンドラチェインという概念を核としたアーキテクチャをとっており,これによってWebサービスのモジュール化が可能となり,拡張性を維持している.
AXISは,ハンドラとハンドラチェインという概念を核としたアーキテクチャをとっている. ハンドラはSOAP電文を処理するモジュールであり,ハンドラチェインはそれらを連ねたものである.
上図のように,チェーンにさらに別のチェーン(図ではハンドラC,Dからなるチェーン)をぶら下げることも可能である. 上図の場合,SOAP電文は,順にハンドラA,B,ピボットハンドラ,そしてサブチェイン上のハンドラC,DそしてハンドラEによって処理される. deploy.wsddにて要素serviceにて指定してきたクラスは,特殊なハンドラで,そのチェーンの中心となるハンドラ,ピボットハンドラという. ピボットハンドラは,deploy.wsddにて指定したメソッドが呼び出されたが,通常のハンドラは,インタフェース
org.apache.axis.Handler
を実装し,AXISによって実装した処理が呼び出される. 本頁では,複数のハンドラによるハンドラチェインによって構成されるWebサービスのサンプルを作成してみた.
今回は,3種類のハンドラと1つのピボットハンドラから二種類の組合せのチェーンを作成し,それぞれWebサービスとするサンプルを作成した. 二つのWebサービスは,クライアントから送信する計算パラメータに「1を足して2倍する」サービスと,「1を足して4倍する」サービスである. それぞれ計算結果を回答する. deploy.wsddは以下.
まず,要素handlerで,3つのハンドラlogger,add1,multiple2を作成した.
  <handler name="logger"
           type="java:org.apache.axis.handlers.LogHandler">
    <parameter name="LogHandler.writeToConsole" value="false"/>
    <parameter name="LogHandler.fileName" value="/tmp/myaxis.log"/>
  </handler>

  <handler name="add1"
           type="java:org.fireproject.axissample.AddHandler">
    <parameter name="addValue" value="1"/>
  </handler>

  <handler name="multiple2"
           type="java:org.fireproject.axissample.MultipleHandler">
    <parameter name="multipleValue" value="2"/>
  </handler>
loggerは,AXISにて提供されているハンドラで,add1,multiple2はサンプルにて自作した「1を足す」ハンドラと「2倍する」ハンドラである. 要素handlerの属性について以下に示す.
name
ハンドラ名
type
java:を頭につけて,ハンドラ実装クラスを示すか,他のhandler要素のname属性の値を記述する.
次に要素multiple4というチェインを定義している.
  <chain name="multiple4">
    <handler type="multiple2"/>
    <handler type="multiple2"/>
  </chain>
「2倍する」ハンドラ二つを組み合わせて「4倍する」チェインを実現している. 最後に「1を足して2倍する」サービスと,「1を足して4倍する」サービスを定義している.
  <service name="Add1AndMultiple2" provider="java:MSG">
    <parameter name="className" value="org.fireproject.axissample.CalculateService"/>
    <parameter name="allowedMethods" value="calculate"/>
    <requestFlow>
      <handler type="add1"/>
      <handler type="multiple2"/>
    </requestFlow>
    <responseFlow>
      <handler type="add1"/>
      <handler name="add1_alt"
               type="java:org.fireproject.axissample.AddHandler">
        <parameter name="addValue" value="-1"/>
      </handler>
      <handler type="logger"/>
    </responseFlow>
  </service>

  <service name="Add1AndMultiple4" provider="java:MSG">
    <parameter name="className" value="org.fireproject.axissample.CalculateService"/>
    <parameter name="allowedMethods" value="calculate"/>
    <requestFlow>
      <handler type="logger"/>
      <handler type="add1"/>
      <chain type="multiple4"/>
    </requestFlow>
    <responseFlow>
      <handler type="logger"/>
    </responseFlow>
  </service>
それぞれ今までのWebサービス定義をしてきた要素serviceの子要素に要素resquestFlowと要素responseFlowを記述し,その子要素として先に定義したハンドラを指定している. 要素service内では,ハンドラチェインはrequesetFlowとresponseFlowに分けられる. それぞれピボットハンドラの前か後かという点が異なる. 処理順としては,requestFlow内のハンドラが記述順に,次にピボットハンドラ,そしてresponseFlow内のハンドラが記述順に処理される. また,ハンドラによっては,ピボットハンドラの前か後かが実装上重要な意味をもつことがある. サービスAdd1AndMultiple2のresponseFlow上のハンドラadd1とadd1_altは,後述するハンドラインスタンスのライフサイクルの確認で使用するもので,サービス処理のための意味はない.
サンプルでは,AddHandlerとMultipleHandlerを作成した. Handlerはインタフェース
org.apache.axis.Handler
を実装する必要があるが,多くの場合このいくつかのメソッドを実装するアブストラクトクラス
org.apache.axis.handlers.BasicHandler
を拡張して実装する. 今回は動作確認用の標準出力を行うSampleHandlerを更に間に挟んでAddHandlerとMultipleHandlerを実装した.
サンプルのハンドラのコードを示す. まずはSampleHandler.
Handlerメソッドにて実装すべきメソッドに関する説明を一通りコメントに記述した. メイン処理を行うinvokeメソッド,SOAPFault時に起動されるonFaultメソッド,そしてオプションのアクセサが重要である. オプションは先のdeploy.wsddに記述した,要素handlerの子要素parameterにて指定でき,AXISによってsetされる. カスタムハンドラでは,要素paramterの属性nameの値を指定してgetOptionすることで,値を取得できる. つぎにAddHandler.
invodkeメソッドのみ実装している. パラメータにあるMessageContextオブジェクトが重要で,これを通して,チェーン上の自分の位置(ピボットハンドラの前後)を判定したり,処理対象メッセージ(要求電文か回答電文)を取得できる. また,ハンドラ間の処理結果の受渡しは,処理対象メッセージを書き換えて行く方法の他に,msgContextにプロパティとして格納する方法がある.
	CalculateParameter cp = (CalculateParameter)msgContext.getProperty(CalculateService.CALCULATE_PARAMETER_KEY);
...省略...
	msgContext.setProperty(CalculateService.CALCULATE_PARAMETER_KEY, result);
これはサーブレットでリクエストやセッションにsetAttribute,getAttributeするイメージに似ている. MultipleHandlerはAddHandlerとほぼ同じ構成である.
LogHandlerは,デバッグ用ユーティリティとしてAXISによって提供されているハンドラである. パラメータLogHandler.writeToConsoleによってコンソール出力かファイル出力かを指定する.
    <parameter name="LogHandler.writeToConsole" value="false"/>
そしてファイル出力の場合,パラメータLogHandler.fileNameによって,出力ファイルを指定する.
    <parameter name="LogHandler.fileName" value="/tmp/myaxis.log"/>
指定しない場合は,
    <parameter name="LogHandler.fileName" value="axis.log"/>
指定したと解釈される. LogHandlerは,requestFlowとresponseFlowの両方,またはresponseFlowのみに指定する. responseFlow上のLogHandlerは,要求電文と回答電文を出力する.
========================================================
In message:
<?xml version="1.0" encoding="utf-8"?>
  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Body>
      <requestMessage>
        <parameter><value>8</value></parameter>
      </requestMessage>
    </soapenv:Body>
  </soapenv:Envelope>
= Out message:
<?xml version="1.0" encoding="UTF-8"?>
  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Body>
      <responceMessage><value>18</value></responceMessage>
    </soapenv:Body>
  </soapenv:Envelope>
=======================================================
画面の都合上,適当に改行している. 両方に指定した場合は,responseFlow上のLogHandlerが,requestFlow上のLogHandlerからresponseFlow上のLogHandlerまでの経過時間も表示する.
=======================================================
= Elapsed: 26 milliseconds
= In message:
<?xml version="1.0" encoding="utf-8"?>
  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Body>
      <requestMessage>
        <parameter><value>7</value></parameter>
      </requestMessage>
    </soapenv:Body>
  </soapenv:Envelope>
= Out message:
<?xml version="1.0" encoding="UTF-8"?>
  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Body>
      <responceMessage><value>32</value></responceMessage>
    </soapenv:Body>
  </soapenv:Envelope>
=======================================================
すなわちreqeustFlow上のLogHandlerは,経過時間計測のための準備処理をするのみである.
    public void invoke(MessageContext msgContext) throws AxisFault {
        log.debug("Enter: LogHandler::invoke");
        if (msgContext.getPastPivot() == false) {
           start = System.currentTimeMillis();
        } else {
            logMessages(msgContext);
        }
        log.debug("Exit: LogHandler::invoke");
    }
致命的欠陥としては,このハンドラはスレッドセーフではないということと,ファイナライズが正しく行われていない点である. どちらも変数startが関わっており,これがインスタンス変数であるのだが,次節のように,AXISでは,同じ要素Handler定義されたものは,同一インスタンスとなる. したがって,同一Webサービスに同時に複数アクセスされると,start変数の値の整合性は保証されない. さらに,変数startは,経過時間出力後初期値-1に戻されないので,サンプルの用に経過時間を出力するAdd1AndMultiple4サービス実行後は,変数startの値が-1でないので,経過時間を出力しないはずのAdd1AndMulptiple2サービスでも経過時間が出力されてしまう. しかもその値は,前回実行したAdd1AndMultiple4サービス開始からの経過時間となってしまう.
SampleHandlerでは,invokeメソッドにおいて
	System.out.println(this);
としている. これによって,作成したハンドラのinvokeメソッドが実行される度に,そのインスタンスを識別できる. 以下にAdd1AndMultiple2,Add1AndMultiple4を順に実行した際のログ出力を示す. まずAdd1AndMultiple2を実行.
invoke called
org.fireproject.axissample.AddHandler@538974
parameter of getOption = addValue
result of getOption = 1
invoke called
org.fireproject.axissample.MultipleHandler@6545d2
parameter of getOption = multipleValue
result of getOption = 2
rootElement = <requestMessage><parameter><value>-2</value></parameter></requestMessage>
invoke called
org.fireproject.axissample.AddHandler@538974
parameter of getOption = addValue
result of getOption = 1
invoke called
org.fireproject.axissample.AddHandler@c1a0eb
parameter of getOption = addValue
result of getOption = -1
そしてAdd1AndMultiple4を実行.
invoke called
org.fireproject.axissample.AddHandler@538974
parameter of getOption = addValue
result of getOption = 1
invoke called
org.fireproject.axissample.MultipleHandler@6545d2
parameter of getOption = multipleValue
result of getOption = 2
invoke called
org.fireproject.axissample.MultipleHandler@6545d2
parameter of getOption = multipleValue
result of getOption = 2
rootElement = <requestMessage><parameter><value>-1</value></parameter></requestMessage>
まず,MultipleHandlerは,一つのインスタンスしかないことが分かる. そしてAddHandlerは2種類のインスタンスがある. MultipleHandlerは一つのハンドラハンドラ定義(multiple4)しかない. AddHandlerは,name属性でいうと,add1とadd1_altの二つのハンドラ定義がある. また,インスタンスの出現順序と定義したチェーンの構成からも,要素handlerによる定義とインスタンスが1対1となっていることがわかる. すなわち,deploy.wsddにおける要素handlerは,そのハンドラのインスタンスを意味すると考えてよい. 属性typeで他の要素handlerを参照する場合は,その参照先の同一インスタンスとなる. 実際のサービスを提供するシステムを開発するにあたっては,特に注意すべき点である.
前節でLogHandleroがスレッドセーフでないと記述した. では,MessageContext#getCurrentContextで取得できるMessageContextオブジェクトはスレッドセーフかどうか,心配になってくる. MessageContextオブジェクトは,この頁のサンプルでいうと,カスタムハンドラのinvokeメソッドの引数で登場した. これは現在処理しているコンテキストを表し,要求電文や回答電文,チェーンにおける該当ハンドラの位置や処理結果の格納などを行える. とにかく非常に重要なクラスである. この頁のサンプルでは,さらにCalculatorServiceにて使用している.
// 先行ハンドラによる処理結果取得
MessageContext context = MessageContext.getCurrentContext();
CalculateParameter allParameter = (CalculateParameter)context.getProperty(CalculateService.CALCULATE_PARAMETER_KEY);
上位から引数で渡されず,どこからでも取得可能なインタフェースとなっている. 非常に怪しい. で,調べてみた. まずMessageContext#getCurrentContext()
    public static MessageContext getCurrentContext() {
       return AxisEngine.getCurrentMessageContext();
    }
追う. AxisEngine#getCurrentMessageContext()
    public static MessageContext getCurrentMessageContext() {
        return (MessageContext) currentMessageContext.get();
    }
さらに追う. AxisEngine#currentMessgeContext
    private static ThreadLocal currentMessageContext = new ThreadLocal();
最終的な格納場所が,ThreadLocalオブジェクト内であった. というわけで,スレッドセーフのようだ.
matsu(C)
Since 2002
Mail to matsu