EJB 3.0(Public Draft)入門記 Simplified API Chapter 4 おまけ JSR-181

前回入門記の「4.1.1 Business Interfaces」でJSR-181アノテーションについてふれられていました。どんなものか気になるので今回はJSR-181のアノテーション(@WebServiceとか@WebMethodとか)を使ってみたいと思います。


まずはHOWTO get started with JBossWSに書いてあるとおりにCVSからjboss-headなるものをcheckoutして自分でbuildしなければいけないようです。私の環境はWindows XP SP2なんですがbuildのbatファイルを実行させるとMS932がナンタラカンタラでうまくいきませんでした。エンコーディングの問題らしい...。実行時にパラメータ渡したり、build.xml内でjavacのencodingを明示的に指定したりして、なんとかbuildしちゃいました。
buildの結果、「jboss-5.0.0alpha」が作られます。まだ4.0.3がfinalリリースされていないのにもう5.0なのかぃ。
ところでJBossって予想以上に巨大な印象です(CVSで管理されているファイルなどの量を見る限り)、アプリケーションサーバというのはそういうものなんでしょうか。


JBossのテストケースを参考に動くものを作ってみます。JBossのテストケースにはJBoss依存のアノテーションが使われているのですが、なんかいやなので依存しないものにしました。ビルドとデプロイには前回までと同じbuild.xmlJBOSS_HOMEのとこだけ変えて使います。実行はAntでできますがEclipseから実行した方が楽だと最近思い始めたのでEclipseから行います。Eclipseのビルドパスをbuild.xmlで指定しているクラスパスと同様にしておきます。ちなみにJSR-181に関係するjarファイルはJBOSS_HOME\clientのjbossws-client.jarのようです。

今回の題材はCalculatorではなくEchoです。最初はCalculatorでやってみたんですがパラメータを複数渡すとうまく動かなくて。とりあえず動かしてみたいので今回はあきらめてもいいかぁ(いいわけ)。作成したコードはこんな感じです。

ビジネスインタフェースWebサービスとしてだけ呼び出されるのならばいらないかもしれませんが一応用意。

public interface Echo {
  String echo(String arg);
}


エンドポイント:targetNamespaceって何にしたらいいのかわからなかったんですが、とりあえずlocalhostを指すようしときました。@WebServiceを使っています。

import java.rmi.Remote;
import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService(name = "EchoEndpoint", targetNamespace = "http://localhost/study", serviceName = "EchoService")
public interface EchoEndpoint extends Remote {
  @WebMethod
  public String echo(String arg);
}


Beanクラス:@WebServiceを使っていますが、endpointInterfaceを示しているだけです。

import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.jws.WebService;

@Stateless
@Remote(Echo.class)
@WebService(endpointInterface="study.ejb.EchoEndpoint")
public class EchoBean implements Echo {
  public String echo(String arg) {
    return arg;
  }
}

必要なものは以上です。jaxrpc-mapping.xmlとかwebservices.xmlはいりません。これらをビルドしてデプロイするとWSDLが自動で生成されてWebサービスが開始されます。


自動生成されたWSDL:自動生成されたWSDLはこんな感じでした(ここにコピペするにあたって下から4行目についてはコンピュータ名をlocalhostに変更してます)。ちなみ下から4行目のにhttp://localhost:8080/study/study.ejb.EchoBeanのコンテキストルートである最初の「study」は自動的にモジュール名(study.ejb3のstudy)となるようです。テストケースをみるかぎりJBossアノテーションを使えば任意の値を指定できるようです。

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="EchoService" 
  targetNamespace="http://localhost/study" 
  xmlns:tns="http://localhost/study" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
  xmlns="http://schemas.xmlsoap.org/wsdl/">
  <types>
  </types>
  <message name="EchoEndpoint_echo">
    <part name="String_1" type="xsd:string"/>
  </message>
  <message name="EchoEndpoint_echoResponse">
    <part name="result" type="xsd:string"/>
  </message>
  <portType name="EchoEndpoint">
    <operation name="echo" parameterOrder="String_1">
      <input message="tns:EchoEndpoint_echo"/>
      <output message="tns:EchoEndpoint_echoResponse"/>
    </operation>
  </portType>
  <binding name="EchoEndpointBinding" type="tns:EchoEndpoint">
    <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="echo">
      <soap:operation soapAction=""/>
      <input>
        <soap:body use="literal" namespace="http://localhost/study"/>
      </input>
      <output>
        <soap:body use="literal" namespace="http://localhost/study"/>
      </output>
    </operation>
  </binding>
  <service name="EchoService">
    <port name="EchoEndpointPort" binding="tns:EchoEndpointBinding">
      <soap:address location="http://localhost:8080/study/study.ejb.EchoBean"/>
    </port>
  </service>
</definitions>


クライアントWebサービスのエンドポイントにアクセスします。JBossのテストケースではInitialContextを使っていましたが、サービスをJNDIにバインディングするためにJBoss固有の機能が使われていたので、InitialContextは使いませんでした。

import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;

public class EchoClient {
  public static void main(String[] args) throws Exception {
    ServiceFactory sf = ServiceFactory.newInstance();
    String wsdlURI = "http://localhost:8080/study/study.ejb.EchoBean?wsdl";
    URL wsdlURL = new URL(wsdlURI);
    Service ots = sf.createService(wsdlURL, new QName("http://localhost/study", "EchoService"));

    EchoEndpoint port = (EchoEndpoint) ots.getPort(EchoEndpoint.class);
    System.out.println(port.echo("Hello!"));
  }
}

クライアントを実行すると期待通り「Hello!」が出力されます。いろいろとわからないことがありましたがとりあえず満足です。


@WebServiceの書き方はいろいろあるようです。今回はインタフェースと実装をわけ、サービス名などの指定はインタフェースのアノテーションに記述しました。実装にだけ@WebServiceを指定し、インタフェースにアノテーションを使用しない書き方もできました。アノテーションをインタフェースに書いたり実装に書いたり選べる点は@Remoteに似ていますね。これなんですけど、選択肢があることはいいことかもしれませんがぼくとしては@WebServiceや@Remoteなどのアノテーションをどこに書くべきか迷ってしまいます。

  • クライアントとの境界を明確にするためにインタフェースにアノテーションを書くべきか?
  • クライアントからの透過的なアクセスを重視し実装クラスにアノテーションを書き、インタフェースはアノテーションなしの通常のインタフェースにすべきか?

そういえばTech Edのあるセッションで「OOでは透過性を重要視していたがSOAではサービスの境界を明確にすることが重要だ」みたいな話がありました。うーむ。


JSR-181のアノテーションEJB 3.0を使ったコードの雰囲気がなんとなくですがつかめました。アノテーショを使わない場合に比べてずいぶん楽だと思います。ところでJSR-181のアノテーションPOJOにも使えるようです。でも疑問なのはJBossのテストコードを見るとこのPOJOがweb.xmlサーブレットとして登録されている点です。そのPOJOはHttpServletやServletをextendやimplementしているわけではないんですけどそれってアリ?