第3章 JAX-WS Web サービスの開発

XML ベースの Web サービス (JAX-WS) 向けの Java API は、Web サービスにアクセスして公開するために使用されるクラスや、AIX と Java 間のマッピングを定義します。JBossWS は最新の JAX-WS specification を実装し、ユーザーはベンダーに依存しない Web サービス使用のニーズを参照できます。JAX-WS の Jakarta EE は Jakarta XML Web Services Specification 2.3 仕様 です。

3.1. JAX-WS ツールの使用

以下の JAX-WS コマンドラインツールは JBoss EAP ディストリビューションに含まれています。これらのツールは、サーバー および クライアント側 の開発にさまざまな方法で使用できます。

表3.1 JAX-WS コマンドラインツール

コマンド説明

wsprovide

JAX-WS の移植可能なアーティファクトを生成し、抽象的なコントラクトを提供します。ボトムアップ開発に使用します。

wsconsume

抽象的なコントラクト (WSDL や Schema ファイル) を使用し、サーバーとクライアントの両方に対してアーティファクトを生成します。トップダウンおよびクライアント開発に使用されます。

これらのツールの使用方法の詳細は、JAX-WS Tools を参照してください。

3.1.1. サーバー側開発ストラテジー

サーバー側で Web サービスエンドポイントを開発する場合、ボトムアップ開発 として知られる Java コードや、トップダウン開発というサービスを定義する WSDL から開始するオプションを利用できます。これが新しいサービスで、既存のコントラクトがない場合、ボトムアップアプローチが最速のルートになります。サービスを稼働させるためには、クラスにアノテーションをいくつか追加する必要があります。ただし、すでに定義されているコントラクトでサービスを開発する場合は、ツールによるアノテーション付きコードの生成を可能にするため、トップダウンアプローチを使用した方がはるかに簡単です。

ボトムアップのユースケース:

  • 既存の EJB3 Bean を Web サービスとして公開
  • 新規サービスの提供と、コントラクトが生成されるようにする。

トップダウンのユースケース:

  • 既存の Web サービスの実装を置き換え、古いクライアントとの互換性を失うことはできません。
  • サードパーティーが指定したコントラクトに準拠するサービスを公開している (たとえば、定義済みプロトコルを使用して返送するベンダー)。
  • 事前に開発した XML Schema と WSDL に準拠するサービスを作成します。
wsprovide を使用したダウンアップグレードストラテジー

ボトムアップストラテジーでは、サービスの Java コードを開発し、JAX-WS アノテーションを使用してアノテーションを付ける必要があります。これらのアノテーションを使用して、サービスに生成されるコントラクトをカスタマイズできます。XML ベースの Web Services の Jakarta EE は Jakarta XML Web Services Specification 2.3 です。たとえば、操作名を任意のものにマッピングするように変更できます。ただし、すべてのアノテーションには適切なデフォルトがあるため、@WebService アノテーションのみが必要になります。

これは、単一クラスを作成するのと同じように簡単に実行できます。

package echo;

@javax.jws.WebService
public class Echo {

   public String echo(String input) {
      return input;
   }
}

デプロイメントはこのクラスを使用して構築でき、JBossWS にデプロイするために必要な唯一の Java コードです。ラッパークラスと呼ばれるその他の Java アーティファクトは、デプロイ時に生成されます。

wsprovide ツールの主な目的は、移植可能な AX-WS アーティファクトを生成することです。さらに、サービスの WSDL ファイルを提供するのに使用できます。これは、-w オプションを使用して wsprovide を呼び出すことで取得できます。

$ javac -d . Echo.java
$ EAP_HOME/bin/wsprovide.sh --classpath=. -w echo.Echo

アクセスメントを検査すると、EchoService という名前のサービスが表示されます。

<wsdl:service name="EchoService">
  <wsdl:port name="EchoPort" binding="tns:EchoServiceSoapBinding">
    <soap:address location="http://localhost:9090/EchoPort"/>
  </wsdl:port>
</wsdl:service>

予想通りに、このサービスは、echo という操作を定義します。

<wsdl:portType name="Echo">
  <wsdl:operation name="echo">
    <wsdl:input name="echo" message="tns:echo">
    </wsdl:input>
    <wsdl:output name="echoResponse" message="tns:echoResponse">
    </wsdl:output>
  </wsdl:operation>
</wsdl:portType>

デプロイ時に、このツールを実行する必要はありません。これは、移植可能なアーティファクトまたはサービスの抽象的なコントラクトを生成する場合にのみ必要です。

デプロイメントの POJO エンドポイントは、簡単な web.xml ファイルに作成できます。

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
  version="2.4">

  <servlet>
    <servlet-name>Echo</servlet-name>
    <servlet-class>echo.Echo</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>Echo</servlet-name>
    <url-pattern>/Echo</url-pattern>
  </servlet-mapping>
</web-app>

web.xml と単一の Java クラスを使用して WAR を作成できます。

$ mkdir -p WEB-INF/classes
$ cp -rp echo WEB-INF/classes/
$ cp web.xml WEB-INF
$ jar cvf echo.war WEB-INF
added manifest
adding: WEB-INF/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/classes/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/classes/echo/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/classes/echo/Echo.class(in = 340) (out= 247)(deflated 27%)
adding: WEB-INF/web.xml(in = 576) (out= 271)(deflated 52%)

WAR は JBoss EAP にデプロイできます。これにより、内部的に wsprovide が呼び出され、その WSDL が生成されます。デプロイメントに成功し、デフォルト設定を使用している場合は、管理コンソールで利用できるようにする必要があります。

注記

移植可能な JAX-WS デプロイメントでは、先に生成されたラッパークラスをデプロイメントに追加することができます。

wsconsume を使用したトップダウンストラテジー

トップダウンの開発ストラテジーは、サービスの抽象的なコントラクトで始まります。これには WSDL ファイルおよびゼロ以上のスキーマファイルが含まれます。wsconsume ツールを使用してこのコントラクトを消費し、アノテーション付きの Java クラスと、それを定義するオプションのプションのソースを生成します。

注記

wsconsume には、Unix システムのシンボリックリンクに問題がある可能性があります。

ボトムアップの例から WSDL ファイルを使用して、このサービスに準拠する新しい Java 実装を生成することができます。-k オプションは wsconsume に渡され、Java クラスのみを提供する代わりに、生成された Java ソースファイルを保存します。

$ EAP_HOME/bin/wsconsume.sh -k EchoService.wsdl

以下の表には、生成される各ファイルの目的をまとめています。

表3.2 生成されるファイル

ファイル目的

Echo.java

サービスエンドポイントインターフェイス

EchoResponse.java

応答メッセージのラッパー Bean

EchoService.java

JAX-WS クライアントによってのみ使用されます。

Echo_Type.java

要求メッセージのラッパー Bean

ObjectFactory.java

JAXB XML レジストリー

package-info.java

JAXB パッケージのアノテーションのホルダー

サービスエンドポイントインターフェイスを調べると、ボトムアップの例で手動で記述したクラスよりも明示的なアノテーションが表示されますが、これらは同じコントラクトに対して評価されます。

@WebService(targetNamespace = "http://echo/", name = "Echo")
@XmlSeeAlso({ObjectFactory.class})
public interface Echo {

    @WebMethod
    @RequestWrapper(localName = "echo", targetNamespace = "http://echo/", className = "echo.Echo_Type")
    @ResponseWrapper(localName = "echoResponse", targetNamespace = "http://echo/", className = "echo.EchoResponse")
    @WebResult(name = "return", targetNamespace = "")
    public java.lang.String echo(
        @WebParam(name = "arg0", targetNamespace = "")
        java.lang.String arg0
    );
}

パッケージ化以外の不明な部分は実装クラスで、上記のインターフェイスを使用して記述できるようになりました。

package echo;

@javax.jws.WebService(endpointInterface="echo.Echo")
public class EchoImpl implements Echo {
   public String echo(String arg0) {
      return arg0;
   }
}

3.1.2. クライアント側の開発ストラテジー

クライアント側の詳細を確認する前に、Web サービスの中心となる切り離す概念を理解することが重要です。Web サービスは、この方法で使用することもできますが、内部 RPC には適していません。CORBA や RMI などのより優れた技術があります。Web サービスは、特に相互運用可能な粒度の細かい対応のために設計されました。Web サービスの対話に参加しているいかなるパーティも、特定のオペレーティングシステム上で実行したり、特定のプログラミング言語で記述されているといった保証はありません。そのため、クライアントおよびサーバー実装は明確に分離することが重要です。一般的なのは、抽象的なコントラクト定義のみです。何らかの理由でソフトウェアがこの原理に準拠しない場合は、Web サービスを使用しないことをお勧めします。上記の理由により、クライアント開発に推奨される方法は、クライアントが同じサーバーで実行されている場合でもトップダウンアプローチに従うことです。

wsconsume を使用したトップダウンストラテジー

このセクションは、サーバー側のトップダウンセクションのプロセスを繰り返しますが、デプロイ済みの WSDL を使用します。これは、デプロイ時に計算される soap:address の正しい値を取得します (下記参照)。この値は、必要に応じて手動で編集できますが、正しいパスを指定する必要があります。

例: デプロイ済みコンポーネントの soap:address

<wsdl:service name="EchoService">
  <wsdl:port name="EchoPort" binding="tns:EchoServiceSoapBinding">
    <soap:address location="http://localhost.localdomain:8080/echo/Echo"/>
  </wsdl:port>
</wsdl:service>

wsconsume を使用して、デプロイされた WSDL の Java クラスを生成します。

$ EAP_HOME/bin/wsconsume.sh -k http://localhost:8080/echo/Echo?wsdl

EchoService.java クラスが、WSDL が取得元の場所を保存する方法に注目してください。

@WebServiceClient(name = "EchoService",
                  wsdlLocation = "http://localhost:8080/echo/Echo?wsdl",
                  targetNamespace = "http://echo/")
public class EchoService extends Service {

    public final static URL WSDL_LOCATION;

    public final static QName SERVICE = new QName("http://echo/", "EchoService");
    public final static QName EchoPort = new QName("http://echo/", "EchoPort");

    ...

    @WebEndpoint(name = "EchoPort")
    public Echo getEchoPort() {
        return super.getPort(EchoPort, Echo.class);
    }

    @WebEndpoint(name = "EchoPort")
    public Echo getEchoPort(WebServiceFeature... features) {
        return super.getPort(EchoPort, Echo.class, features);
    }
}

ご覧のとおり、この生成されたクラスは JAX-WS、javax.xml.ws.Service の主なクライアントエントリーポイントを拡張します。Service を直接使用することはできますが、設定情報を提供するため、これははるかに簡単です。サービスエンドポイントインターフェイスのインスタンスを返す getEchoPort() メソッドに注意してください。返されたインターフェイスでメソッドを呼び出すと、Web サービスの操作をすべて呼び出すことができます。

重要

実稼働環境のアプリケーションで、リモート WSDL URL を参照しないでください。これにより、Service オブジェクトをインスタンス化するたびにネットワーク I/O が発生します。代わりに、保存されたローカルコピーでツールを使用するか、コンストラクターの URL バージョンを使用して、新しい WSDL の場所を提供します。

クライアントの書き込みとコンパイル:

import echo.*;

public class EchoClient {

   public static void main(String args[]) {

      if (args.length != 1) {
          System.err.println("usage: EchoClient <message>");
          System.exit(1);
      }

      EchoService service = new EchoService();
      Echo echo = service.getEchoPort();
      System.out.println("Server said: " + echo.echo(args0));
   }
}

以下のように ENDPOINT_ADDRESS_PROPERTY を設定すると、ランタイム時に操作のエンドポイントアドレスを変更できます。

EchoService service = new EchoService();
Echo echo = service.getEchoPort();

/* Set NEW Endpoint Location */
String endpointURL = "http://NEW_ENDPOINT_URL";
BindingProvider bp = (BindingProvider)echo;
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointURL);

System.out.println("Server said: " + echo.echo(args0));