3.3. JAX-WS Web サービスクライアント

3.3.1. JAX-WS Web サービスの消費とアクセス

Web サービスエンドポイントの作成後、手動で、あるいは JAX-WS アノテーションを使用して、その WSDL にアクセスできます。XML ベースの Web Services の Jakarta EE は Jakarta Enterprise Web Services 1.4 仕様 です。これは、Web サービスと通信する基本的なクライアントアプリケーションを作成するのに使用できます。パブリッシュされた WSDL から Java コードを生成するプロセスは、Web サービスを使用すると呼ばれます。これは以下の場合に発生します。

クライアントアーティファクトの作成

クライアントアーティファクトを作成する前に、WSDL コントラクトを作成する必要があります。以下の WSDL コントラクトは、本セクションの残りの部分に記載の例に使用します。

以下の例は、ProfileMgmtService.wsdl ファイルにこの WSDL コントラクトがあることを前提としています。

<definitions
    name='ProfileMgmtService'
    targetNamespace='http://org.jboss.ws/samples/retail/profile'
    xmlns='http://schemas.xmlsoap.org/wsdl/'
    xmlns:ns1='http://org.jboss.ws/samples/retail'
    xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/'
    xmlns:tns='http://org.jboss.ws/samples/retail/profile'
    xmlns:xsd='http://www.w3.org/2001/XMLSchema'>

   <types>

      <xs:schema targetNamespace='http://org.jboss.ws/samples/retail'
                 version='1.0' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
         <xs:complexType name='customer'>
            <xs:sequence>
               <xs:element minOccurs='0' name='creditCardDetails' type='xs:string'/>
               <xs:element minOccurs='0' name='firstName' type='xs:string'/>
               <xs:element minOccurs='0' name='lastName' type='xs:string'/>
            </xs:sequence>
         </xs:complexType>
      </xs:schema>

      <xs:schema
          targetNamespace='http://org.jboss.ws/samples/retail/profile'
          version='1.0'
          xmlns:ns1='http://org.jboss.ws/samples/retail'
          xmlns:tns='http://org.jboss.ws/samples/retail/profile'
          xmlns:xs='http://www.w3.org/2001/XMLSchema'>

         <xs:import namespace='http://org.jboss.ws/samples/retail'/>
         <xs:element name='getCustomerDiscount'
                     nillable='true' type='tns:discountRequest'/>
         <xs:element name='getCustomerDiscountResponse'
                     nillable='true' type='tns:discountResponse'/>
         <xs:complexType name='discountRequest'>
            <xs:sequence>
               <xs:element minOccurs='0' name='customer' type='ns1:customer'/>

            </xs:sequence>
         </xs:complexType>
         <xs:complexType name='discountResponse'>
            <xs:sequence>
               <xs:element minOccurs='0' name='customer' type='ns1:customer'/>
               <xs:element name='discount' type='xs:double'/>
            </xs:sequence>
         </xs:complexType>
      </xs:schema>

   </types>

   <message name='ProfileMgmt_getCustomerDiscount'>
      <part element='tns:getCustomerDiscount' name='getCustomerDiscount'/>
   </message>
   <message name='ProfileMgmt_getCustomerDiscountResponse'>
      <part element='tns:getCustomerDiscountResponse'
            name='getCustomerDiscountResponse'/>
   </message>
   <portType name='ProfileMgmt'>
      <operation name='getCustomerDiscount'
                 parameterOrder='getCustomerDiscount'>

         <input message='tns:ProfileMgmt_getCustomerDiscount'/>
         <output message='tns:ProfileMgmt_getCustomerDiscountResponse'/>
      </operation>
   </portType>
   <binding name='ProfileMgmtBinding' type='tns:ProfileMgmt'>
      <soap:binding style='document'
                    transport='http://schemas.xmlsoap.org/soap/http'/>
      <operation name='getCustomerDiscount'>
         <soap:operation soapAction=''/>
         <input>

            <soap:body use='literal'/>
         </input>
         <output>
            <soap:body use='literal'/>
         </output>
      </operation>
   </binding>
   <service name='ProfileMgmtService'>
      <port binding='tns:ProfileMgmtBinding' name='ProfileMgmtPort'>

         <!-- service address will be rewritten to actual one when WSDL is requested from running server -->
         <soap:address location='http://SERVER:PORT/jaxws-retail/ProfileMgmtBean'/>
      </port>
   </service>
</definitions>
注記

JAX-WS アノテーションを使用して Web サービスのエンドポイントを作成すると、WSDL コントラクトは自動的に生成されます。その URL のみが必要になります。この URL を見つけるには、Runtime に移動して、該当するサーバーを選択し、Webservicesを 選択してエンドポイントを選択します。

Wsconsume.sh ツールまたは wsconsume.bat ツールは、抽象的なコントラクト (WSDL) を消費し、アノテーション付きの Java クラスと、これを定義するオプションのソースを生成するために使用されます。このファイルは EAP_HOME/bin/ ディレクトリーにあります。

$ ./wsconsume.sh --help
WSConsumeTask is a cmd line tool that generates portable JAX-WS artifacts from a WSDL file.

usage: org.jboss.ws.tools.cmd.WSConsume [options] <wsdl-url>

options:
    -h, --help                  Show this help message
    -b, --binding=<file>        One or more JAX-WS or Java XML Binding files
    -k, --keep                  Keep/Generate Java source
    -c  --catalog=<file>        Oasis XML Catalog file for entity resolution
    -p  --package=<name>        The target package for generated source
    -w  --wsdlLocation=<loc>    Value to use for @WebService.wsdlLocation
    -o, --output=<directory>    The directory to put generated artifacts
    -s, --source=<directory>    The directory to put Java source
    -t, --target=<2.0|2.1|2.2>  The JAX-WS target
    -q, --quiet                 Be somewhat more quiet
    -v, --verbose               Show full exception stack traces
    -l, --load-consumer         Load the consumer and exit (debug utility)
    -e, --extension             Enable SOAP 1.2 binding extension
    -a, --additionalHeaders     Enable processing of implicit SOAP headers
    -n, --nocompile             Do not compile generated sources

以下のコマンドは、出力に一覧表示されている .java ファイルを ProfileMgmtService.wsdl ファイルから生成します。ソースは、-p スイッチで指定されたパッケージのディレクトリー構造を使用します。

[user@host bin]$ wsconsume.sh -k -p org.jboss.test.ws.jaxws.samples.retail.profile ProfileMgmtService.wsdl
output/org/jboss/test/ws/jaxws/samples/retail/profile/Customer.java
output/org/jboss/test/ws/jaxws/samples/retail/profile/DiscountRequest.java
output/org/jboss/test/ws/jaxws/samples/retail/profile/DiscountResponse.java
output/org/jboss/test/ws/jaxws/samples/retail/profile/ObjectFactory.java
output/org/jboss/test/ws/jaxws/samples/retail/profile/ProfileMgmt.java
output/org/jboss/test/ws/jaxws/samples/retail/profile/ProfileMgmtService.java
output/org/jboss/test/ws/jaxws/samples/retail/profile/package-info.java
output/org/jboss/test/ws/jaxws/samples/retail/profile/Customer.class
output/org/jboss/test/ws/jaxws/samples/retail/profile/DiscountRequest.class
output/org/jboss/test/ws/jaxws/samples/retail/profile/DiscountResponse.class
output/org/jboss/test/ws/jaxws/samples/retail/profile/ObjectFactory.class
output/org/jboss/test/ws/jaxws/samples/retail/profile/ProfileMgmt.class
output/org/jboss/test/ws/jaxws/samples/retail/profile/ProfileMgmtService.class
output/org/jboss/test/ws/jaxws/samples/retail/profile/package-info.class

.java ソースファイルとコンパイルされた .class ファイルはどちらも、コマンドを実行するディレクトリー内の output/ ディレクトリーに生成されます。

表3.3 wsconsume.sh で作成されるアーティファクトの説明

ファイル説明

ProfileMgmt.java

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

Customer.java

カスタムデータタイプ。

Discount.java

カスタムデータタイプ。

ObjectFactory.java

JAXB XML レジストリー。

package-info.java

JAXB パッケージのアノテーション。

ProfileMgmtService.java

サービスファクトリー。

wsconsume コマンドは、すべてのカスタムデータタイプ (JAXB アノテーションが付けられたクラス)、サービスエンドポイントインターフェイス、およびサービスファクトリークラスを生成します。これらのアーティファクトは、Web サービスクライアントの実装をビルドするために使用されます。

サービススタブの設定

Web サービスクライアントはサービススタブを使用して、リモート Web サービス呼び出しの詳細を抽象化します。クライアントアプリケーションの場合、Web サービスの呼び出しは他のビジネスコンポーネントの呼び出しと同様になります。この場合、サービスエンドポイントインターフェイスはビジネスインターフェイスとして機能し、サービスファクトリークラスはサービススタブとしての構築に使用されません。

以下の例では、まずは WSDL の場所とサービス名を使用してサービスファクトリーを作成します。次に、 wsconsume により作成されたサービスエンドポイントインターフェイスを使用してサービススタブを構築します。最後に、スタブは他のビジネスインターフェイスと同様に使用できます。

JBoss EAP 管理コンソールでは、エンドポイントの WSDL URL を確認できます。この URL を見つけるには、Runtime に移動して、該当するサーバーを選択し、Webservicesを 選択してエンドポイントを選択します。

import javax.xml.ws.Service;
[...]
Service service = Service.create(
new URL("http://example.org/service?wsdl"),
new QName("MyService")
);
ProfileMgmt profileMgmt = service.getPort(ProfileMgmt.class);

// Use the service stub in your application

3.3.2. JAX-WS クライアントアプリケーションの開発

クライアントは Java Enterprise Edition 7 コンテナーにデプロイされた JAX-WS エンドポイントと通信し、そこから要求を行います。以下のクラス、メソッド、およびその他の実装の詳細は、JBoss EAP に含まれる Javadocs バンドルの関連セクションを参照してください。

概要

Service は、WSDL サービスを表す抽象化です。WSDL サービスは、一連の関連ポートです。それぞれには、特定のプロトコルとエンドポイントアドレスに結合したポートタイプが含まれます。

通常、サービスの生成は、残りのコンポーネントのスタブが、既存の WSDL コントラクトから生成されると行われます。WSDL コントラクトは、デプロイされたエンドポイントの WSDL URL 経由で利用できます。または、EAP_HOME/bin/ ディレクトリーの wsprovide ツールを使用してエンドポイントソースから作成できます。

このタイプの使用は、静的なユースケースと呼ばれます。この場合、コンポーネントのスタブのいずれかとして作成された Service クラスのインスタンスを作成します。

Service.create メソッドを使用してサービスを手動で作成することもできます。これは、動的なユースケースと呼ばれます。

使用方法
静的ユースケース

JAX-WS クライアントの静的ユースケースでは、すでに WSDL コントラクトが存在することを前提としています。これは外部ツールで生成されるか、JAX-WS エンドポイントの作成時に適切な JAX-WS アノテーションを使用して生成される可能性があります。

コンポーネントのスタブを生成するには、EAP_HOME/bin に含まれる wsconsume ツールを使用します。このツールは、コンテナー URL またはファイルをパラメーターとして取り、ディレクトリーツリーで構造化された複数のファイルを生成します。Service を表すソースおよびクラスファイルはそれぞれ _Service.java_Service.class です。

生成された実装クラスには、引数のないパブリックコンストラクターと、2 つの引数を持つコンストラクターがあります。これら 2 つの引数はそれぞれ WSDL の場所 (java.net.URL) とサービス名 (javax.xml.namespace.QName) を表します。

引数なしのコンストラクターは最もよく使用されます。この場合、WSDL の場所とサービス名は WSDL にあるものです。これらは、生成されたクラスをデコレートする @WebServiceClient アノテーションから暗黙的に設定されます。

@WebServiceClient(name="StockQuoteService", targetNamespace="http://example.com/stocks", wsdlLocation="http://example.com/stocks.wsdl")
public class StockQuoteService extends javax.xml.ws.Service
{
   public StockQuoteService() {
      super(new URL("http://example.com/stocks.wsdl"), new QName("http://example.com/stocks", "StockQuoteService"));
   }

   public StockQuoteService(String wsdlLocation, QName serviceName) {
      super(wsdlLocation, serviceName);
   }
   ...
}

サービスからポートを取得する方法と、ポートでの操作の呼び出し方法に関する詳細は、Dynamic Proxy を参照してください。XML ペイロードを直接使用するか、SOAP メッセージ全体の XML 表現を使用する方法は、Dispatch を参照してください。

動的ユースケース

動的の場合、スタブは自動的に生成されません。代わりに、Web サービスクライアントは Service.create メソッドを使用して Service インスタンスを作成します。以下のコードはこのプロセスを示しています。

URL wsdlLocation = new URL("http://example.org/my.wsdl");
QName serviceName = new QName("http://example.org/sample", "MyService");
Service service = Service.create(wsdlLocation, serviceName);
ハンドラー解決

JAX-WS は、ハンドラーと呼ばれるメッセージ処理モジュールに柔軟なプラグインフレームワークを提供します。これらのハンドラーは JAX-WS ランタイムシステムの機能を拡張します。Service インスタンスは、getHandlerResolversetHandlerResolver メソッドのペア経由で HandlerResolver へのアクセスを提供し、サービス別、ポート別、またはプロトコル別のバインディングベースでハンドラーのセットを設定することができます。

Service インスタンスでプロキシーまたは Dispatch インスタンスを作成すると、現在サービスに登録されているハンドラーリゾルバーが必要なハンドラーチェーンを作成します。Service インスタンスに設定されたハンドラーリゾルバーへの変更は、以前に作成されたプロキシーまたは Dispatch インスタンスでのハンドラーには影響を与えません。

Executor

Service インスタンスは、java.util.concurrent.Executor を使用して設定できます。Executor は、アプリケーションによってリクエストされた非同期コールバックを呼び出します。ServicesetExecutor および getExecutor メソッドは、サービスに設定された Executor を変更および取得できます。

動的プロキシー

動的プロキシーは、Service で提供される getPort メソッドのいずれかを使用するクライアントプロキシーのインスタンスです。portName は、サービスが使用する WSDL ポートの名前を指定します。serviceEndpointInterface は、作成された動的プロキシーインスタンスでサポートされるサービスエンドポイントインターフェイスを指定します。

public <T> T getPort(QName portName, Class<T> serviceEndpointInterface)
public <T> T getPort(Class<T> serviceEndpointInterface)

Service Endpoint Interface は通常 wsconsume ツールを使用して生成されます。これは、WSDL を解析し、そこから Java クラスを作成します。

ポートを返す、タイプ指定されたメソッドも提供されます。これらのメソッドは、SEI を実装する動的プロキシーも返します。以下の例を参照してください。

@WebServiceClient(name = "TestEndpointService", targetNamespace = "http://org.jboss.ws/wsref",
   wsdlLocation = "http://localhost.localdomain:8080/jaxws-samples-webserviceref?wsdl")

public class TestEndpointService extends Service {
    ...

    public TestEndpointService(URL wsdlLocation, QName serviceName) {
        super(wsdlLocation, serviceName);
    }

    @WebEndpoint(name = "TestEndpointPort")
    public TestEndpoint getTestEndpointPort() {
        return (TestEndpoint)super.getPort(TESTENDPOINTPORT, TestEndpoint.class);
    }
}
@WebServiceRef

@WebServiceRef アノテーションは Web サービスへの参照を宣言します。これは、JSR 250 で定義された javax.annotation.Resource アノテーションによって示されるリソースパターンに従います。これらのアノテーションの Jakarta EE と同等の Jakarta EE は、Jakarta Annotations 1.3 仕様 に含まれています。

  • これを使用して、生成される Service クラスであるタイプのリファンレスを定義できます。この例では、type および value 要素はそれぞれ、生成された Service クラスターイプを参照します。さらに、リファンレンスタイプがアノテーションが適用されるフィールドまたはメソッド宣言によって推定される場合、type および value 要素にはデフォルト値の Object.class が設定されることがあります。これは必須ではありません。タイプを推定できない場合、type 要素がデフォルト以外の値を持つ必要があります。
  • これは、タイプが SEI のリファンレスを定義するのに使用できます。この場合、リファレンスのタイプが annotated フィールドまたは method 宣言から推測される場合、type 要素はデフォルト値で存在することがあります (ただし、必須ではありません)。ただし、value 要素は常に存在し、javax.xml.ws.Service ファイルのサブタイプである、生成されたサービスクラスターイプを参照する必要があります。wsdlLocation 要素が存在する場合は、参照される生成サービスクラスの @WebService アノテーションで指定された WSDL の場所情報を上書きします。

    public class EJB3Client implements EJB3Remote
    {
       @WebServiceRef
       public TestEndpointService service4;
    
       @WebServiceRef
       public TestEndpoint port3;
     }
Dispatch

XML Web サービスは、Java EE コンテナーおよびすべてのクライアントにデプロイされる、エンドポイント間の通信に XML メッセージを使用します。XML メッセージは、Simple Object Access Protocol (SOAP) と呼ばれる XML 言語を使用します。JAX-WS API は、エンドポイントとクライアントのメカニズムを提供し、各エンドポイントが SOAP メッセージを送受信できるようにします。マーシャリングは、Java オブジェクトを SOAP XML メッセージに変換するプロセスです。マーシャリング解除とは、SOAP XML メッセージを Java オブジェクトに変換するプロセスのことです。

変換の結果ではなく、raw SOAP メッセージ自体にアクセスする必要がある場合があります。Dispatch クラスはこの機能を提供します。Dispatch は、以下の定数のいずれかによって識別される、2 つの使用モードのいずれかで動作します。

  • javax.xml.ws.Service.Mode.MESSAGE: このモードは、クライアントアプリケーションがプロトコル固有のメッセージ構造で直接動作するようにします。SOAP プロトコルバインディングを使用すると、クライアントアプリケーションは SOAP メッセージで直接機能します。
  • javax.xml.ws.Service.Mode.PAYLOAD: このモードでは、クライアントがペイロード自体と動作します。たとえば、SOAP プロトコルバインディングと使用すると、クライアントアプリケーションは SOAP メッセージ全体ではなく SOAP ボディーのコンテンツで動作します。

Dispatch は低レベルの API で、クライアントはメッセージまたはペイロードを XML として設定する必要があります。これは、個別のプロトコルの標準と、メッセージまたはペイロード構造の詳細な知識とメッセージに忠実に従った状態を意味します。Dispatch は、メッセージの入出力または、任意のタイプのメッセージペイロードに対応した汎用クラスです。

Service service = Service.create(wsdlURL, serviceName);
Dispatch dispatch = service.createDispatch(portName, StreamSource.class, Mode.PAYLOAD);

String payload = "<ns1:ping xmlns:ns1='http://oneway.samples.jaxws.ws.test.jboss.org/'/>";
dispatch.invokeOneWay(new StreamSource(new StringReader(payload)));

payload = "<ns1:feedback xmlns:ns1='http://oneway.samples.jaxws.ws.test.jboss.org/'/>";
Source retObj = (Source)dispatch.invoke(new StreamSource(new StringReader(payload)));
非同期呼び出し

BindingProvider インターフェイスは、クライアントが使用できるプロトコルバインディングを提供するコンポーネントを表します。これはプロキシーによって実装され、Dispatch インターフェイスによって拡張されます。

BindingProvider インスタンスは非同期操作機能を提供する可能性があります。非同期操作の呼び出しは、呼び出し時に BindingProvider インスタンスから切り離されます。操作が完了しても、応答コンテキストは更新されません。代わりに、Response インターフェイスを使用して個別の応答コンテキストを利用できます。

public void testInvokeAsync() throws Exception {
   URL wsdlURL = new URL("http://" + getServerHost() + ":8080/jaxws-samples-asynchronous?wsdl");
   QName serviceName = new QName(targetNS, "TestEndpointService");
   Service service = Service.create(wsdlURL, serviceName);
   TestEndpoint port = service.getPort(TestEndpoint.class);
   Response response = port.echoAsync("Async");
   // access future
   String retStr = (String) response.get();
   assertEquals("Async", retStr);
}
@oneway 呼び出し

@oneway アノテーションは、指定の web メソッドが入力メッセージを取得し、出力メッセージを返しないことを示します。通常、@Oneway メソッドは、ビジネスメソッドの実行前に、制御のスレッドを呼び出しアプリケーションに戻します。

@WebService (name="PingEndpoint")
@SOAPBinding(style = SOAPBinding.Style.RPC)
public class PingEndpointImpl {
   private static String feedback;

   @WebMethod
   @Oneway
   public void ping() {
      log.info("ping");
      feedback = "ok";
   }

   @WebMethod
   public String feedback() {
      log.info("feedback");
      return feedback;
   }
}
タイムアウトの設定

2 種類のプロパティーが HTTP 接続のタイムアウトの動作と、メッセージの受信を待機しているクライアントのタイムアウトを制御します。最初のプロパティーは、javax.xml.ws.client.connectionTimeout で、次のプロパティーは、javax.xml.ws.client.receiveTimeout です。それぞれはミリ秒単位で示され、正しい構文を以下に示します。

public void testConfigureTimeout() throws Exception {
   //Set timeout until a connection is established
   ((BindingProvider)port).getRequestContext().put("javax.xml.ws.client.connectionTimeout", "6000");

   //Set timeout until the response is received
   ((BindingProvider) port).getRequestContext().put("javax.xml.ws.client.receiveTimeout", "1000");

   port.echo("testTimeout");
}