5.3. SOAP Web Service(サーバ/クライアント)

5.3.1. Overview

本節では、SOAP Web Serviceの基本的な概念とJakarta XML Web Servicesを使用したSOAPサーバ、クライアント双方の開発について説明する。

実装に対する具体的な説明については、

  • Jakarta XML Web Servicesを使用したSOAP Web Serviceのアプリケーション構成やAPIの実装方法について説明している。

を参照されたい。


5.3.1.1. SOAPとは

SOAPとは、XMLで記述されたメッセージをコンピュータ間で送受信を行うためのプロトコルあり、W3Cによって仕様が定義されている。
当初、「SOAP」は「Simple Object Access Protocol」の頭字語であったが、現在はそうではないとされている。
SOAPの仕様についての詳細は、W3C -SOAP Specifications-を参照されたい。
本ガイドラインでは、以下の図のような構成でのSOAP Web Serviceを行う場合を想定して説明する。
ただし、下記の構成以外でのSOAP Web Serviceの場合にも応用可能である。(例:クライアントがバッチの場合など)
Server and Client for SOAP

項番

説明

(1)
クライアントは、別のSOAPサーバへの通信を行うWebアプリケーションを想定している。
クライアントと呼んでいるがWebアプリケーション想定なので注意が必要である。
(2)
SOAPサーバは、Webサービスを公開し、クライアントからのSOAP Web ServiceによるXMLを受信して処理を行う。データベースなどにアクセスを行い、業務処理を行うことを想定している。
(3)
SOAP Web ServiceではXMLを使用して情報のやり取りを行う。
今回の想定では、SOAPサーバ、クライアントどちらもJavaである想定としているが、他のプラットフォームでも問題なく通信可能である。

5.3.1.2. SOAPとJakarta XML Web Services

Jakarta XML Web Servicesとは、SOAPなどを使ったWebサービスを扱うためのJava標準APIである。
Jakarta XML Web Servicesを用いることで、JavaのオブジェクトをSOAPの仕様に沿ったXMLに変換して送信することが可能である。
そのため、Jakarta XML Web Servicesを利用してメッセージの送受信を行う場合、利用者はXMLの構造をあまり意識せずデータを扱うことができる。
また、SOAPサーバ、クライアントの公開に関しても、通常のWebアプリケーション同様に、ブランクプロジェクト内のwebプロジェクトから作成したWARファイルをAPサーバにデプロイすることで、SOAP Web Serviceを簡単に実現することができる。

5.3.1.3. 本ガイドラインにおいて利用するJakarta XML Web Services実装ライブラリについて

Macchinetta Server Framework (1.x) 1.11 までは、Spring Frameworkが提供するJakarta XML Web Servicesの連携機能を利用した実装を推奨していたが、Spring Framework 6から、org.springframework.remotingパッケージが削除され、Jakarta XML Web Servicesの連携機能が利用できなくなった。(Spring#27422
それに伴い、Macchinetta Server Framework (1.x) 1.11 からはJakarta XML Web Servicesでの実装を推奨している。

システムで利用するJakarta XML Web Servicesの実装として、どのライブラリを採用すべきかは、どのAPサーバを利用するかに依存する事が多い。(APサーバ側からJakarta XML Web Servicesの実装ライブラリが提供されることが多いため)
本ガイドラインでは、APサーバはTomcat、Jakarta XML Web Services実装としてApache CXF、通信プロトコルはSOAP1.2の利用を想定して説明を行う。
APサーバやJakarta XML Web Services実装ライブラリとして異なるものを使う際には、設定などが異なることがあるので、そちらのマニュアルを参照する方がよい。
TomcatはJakarta XML Web Servicesの実装ライブラリを提供しないため、「アプリケーションの設定」に示すようにApache CXFを依存性に追加し、設定を行う必要があるため注意すること。
また、SpringとJakarta XML Web Servicesを利用して開発を行う場合、以下の内容を留意する必要がある。
SpringとJakarta XML Web Services実装ライブラリを併用する場合の留意事項
  • WebServiceインターフェイス実装クラスはSpringのControllerクラスではないため、 @ControllerAdvice@ExceptionHandler などが適用されない。

  • WebServiceインターフェイス実装クラスがSpringのDIコンテナ上で管理されないため、Springが管理するBeanのインジェクションや、AOPによる横断的な処理をかけることができない。(Spring統合が可能なJakarta XML Web Services実装ライブラリを利用すれば解消される)

Apache CXFは、Spring統合が可能なJakarta XML Web Services実装ライブラリであるため、2点目に記載しているBeanのインジェクションや、AOPによる横断的な処理をかけることができないという制約は解消される。

また、上記制約以外にもAPサーバのJakarta XML Web Services実装によっては、Jakarta XML Web Services仕様への対応状況や実際のWebサービスの動作が異なる場合があり、必ずしも本ガイドラインの実装が全てのAPサーバで同様に動作するわけではない。
開発を始める前には必ず、利用するAPサーバのマニュアルを確認されたい。
コードファーストベースの開発
SOAP Web Serviceの開発では、コードファーストベースの開発と、契約ファーストベースの開発の2つのアプローチがある。
コードファーストは、アノテーション等を使いプログラムを作成してから、それに基づいてWebサービスのWSDLファイル(契約)を自動生成するアプローチである。
それに対して契約ファーストは、最初にWSDLファイル(契約)でメッセージのインターフェース定義を行い、その契約に基づいてプログラムを生成するアプローチである。
本ガイドラインでは、WSDLファイルの記法をあまり意識しないで開発が行えるコードファーストベースでの方法を紹介する。

5.3.1.4. Jakarta XML Web Servicesを利用したSOAP Web Serviceの開発について

SOAPサーバ・クライアントの開発についての例として、SOAPサーバ・クライアントのプロジェクト構成と、SOAPサーバ・クライアントで利用するクラスと処理の流れを説明する。


SOAPサーバ・クライアントのプロジェクト構成

Jakarta XML Web Servicesを利用したWebサービスを作成する場合、既存のブランクプロジェクトとは別に以下2つのプロジェクトを追加することを推奨する。

  • modelプロジェクト

  • webserviceプロジェクト

modelプロジェクトは、Webサービスの引数や返り値に使用するDomain Objectを格納する。
webserviceプロジェクトは、Webサービスを呼び出すインターフェースを格納する。
この2つはSOAPサーバからクライアントに配布する必要があるクラスのみ格納するプロジェクトである。
配布する範囲を明確に識別するため、別プロジェクトにすることを推奨している。

本ガイドラインでは、マルチプロジェクトで以下のような構成を用いる。

ここでもクライアントはWebアプリケーションであることを前提とするが、デスクトップアプリケーションやコマンドラインインターフェースから呼び出す場合も基本的な考え方は同じである。

Server and Client Projects for SOAP

項番

説明

(1)
クライアントを作成する場合、従来のマルチプロジェクトにSOAPサーバから提供されるmodelプロジェクトとwebserviceプロジェクトを追加する。
ここではサーバとクライアントをともに開発することを前提としている。
これらのプロジェクトの詳細については「SOAPサーバの作成」で説明する。
プロジェクトの追加方法については「SOAPサーバ用にプロジェクトの設定を変更する」に記載しているので、開発を行う前に実施すること。

サーバとクライアントの開発が別々で、modelプロジェクトとwebserviceプロジェクトが提供されない場合、もしくはJava以外でSOAPサーバが作成されている場合には、modelプロジェクト内のDomain Objectとwebserviceプロジェクト内のWebサービスインターフェースを自分で作成する必要がある。
(2)
SOAPサーバを作成する場合、従来のマルチプロジェクトに追加してmodelプロジェクトとwebserviceプロジェクトを追加する。
クライアントにこれら2つのプロジェクトを公開する。
クライアントへのmodelプロジェクト、webserviceプロジェクトの公開方法は、Mavenの依存関係への追加を想定している。

結果として、プロジェクトは次のような構成となる。
以下は、SOAPサーバのプロジェクト構成である。
Package explorer for SOAP server projects

以下は、クライアントのプロジェクト構成である。

Package explorer for SOAP client projects

SOAPサーバ・クライアントで利用するクラスと処理の流れ

以下はJakarta XML Web Services実装ライブラリを用いた場合のSOAPサーバ/クライアントにおいて、SOAP Webサービスの呼び出しがどのように行われるかを示した図である。開発者が実装すべき箇所と、Jakarta XML Web Services実装が提供する箇所は、色を分けて表現している。
ここではSOAPのクライアント(図左)であるWebアプリケーションがSOAPサーバ(図右)にアクセスすることを前提としている。
Server and Client Projects for SOAP

項番

説明

(1)
[クライアント] ControllerがServiceを呼び出す。
通常の呼び出しと変更点は特にない。
(2)
[クライアント] ServiceがSOAPサーバ提供側で用意したWebServiceインターフェースを呼び出す。
この図では、ServiceがWebServiceインターフェースを呼び出しているが、要件に応じてControllerから直接WebServiceインターフェースを呼び出してもよい。
(3)
[クライアント] WebServiceインターフェースが呼び出されると実体として「動的プロキシ(Dynamic Proxy)」(以下「プロキシ」)が呼び出される。
このプロキシはjakarta.xml.ws.Serviceが生成したWebServiceインターフェースの実装クラスである。
この実装クラスがServiceにインジェクションされ、ServiceはWebServiceインターフェースのメソッドを呼び出すだけで、SOAP Web Serviceを利用した処理を行うことができる。
(4)
プロキシが、SOAPサーバのWebServiceインターフェースを呼び出す。
SOAPサーバとクライアントでの値のやり取りはDomain Objectを使用して行う。

Note

厳密には、SOAPサーバとクライアントはXMLを使用して通信を行っている。 送信時、および受信時にはJAXBを使用して、Domain ObjectとXMLの相互変換が行われているが、SOAP Web Service作成者はXMLをあまり意識せず、開発を行うことができるようになっている。

(5)
[サーバ] WebServiceインターフェースが呼び出されると実体としてWebService実装クラスが呼び出される。
SOAPサーバでは、WebServiceインターフェースの実装クラスとしてWebService実装クラスを用意する。
このWebService実装クラスは、org.springframework.web.context.support.SpringBeanAutowiringSupportを継承することで、SpringのDIコンテナ上のBeanを@Autowiredなどでインジェクションすることができる。

Note

本ガイドラインにおいて利用するJakarta XML Web Services実装ライブラリについて」のとおりJakarta XML Web Servicesの実装によっては、SpringのDIコンテナ上のBeanをインジェクションできない場合があるため、留意する必要がある。

また、SOAPサーバは、@Injectではなく、@Autowiredでインジェクションすることを推奨する。

@Injectの場合、Jakarta EE(Java EE)サーバが提供するDI機能で使用されるため、Jakarta EE(Java EE)サーバのDIコンテナに存在しないとエラーになってしまう。

上記に対して、@AutowiredであればSpringのDI機能のみで使用されるため、意図せずJakarta EE(Java EE)サーバのDI機能でエラーになるのを防止することができる。

(6)
[サーバ] WebService実装クラスでは、業務処理を行うServiceを呼び出す。
(7)
[サーバ] Serviceでは、Repositoryなどを使用して業務処理を実行する。
通常の呼び出しと変更点は特にない。

5.3.1.5. SOAP Web Serviceとして公開されるURL

SOAP Web Serviceを作成するとWSDL(Web Services Description Language)というWebサービスのインターフェース定義が公開され、クライアントはこの定義をもとにSOAP Web Serviceを実行する。
WSDLの詳細は、W3C -Web Services Description Language (WSDL)-を参照されたい。
WSDL内には、Webサービス実行時のアクセスURLやメソッド名、引数、戻り値などが定義される。
本ガイドラインの通りにSOAP Web Serviceを作成すると、以下のURLでWSDLが公開される。
クライアントではこのURLを指定するか、取得したWSDLファイルをクライアントに配置し、そのファイルパスを指定する必要がある。
  • http://AAA.BBB.CCC.DDD:XXXX/[server projectName]-web/ws/TodoWebService?wsdl

WSDL内で定義されるエンドポイントアドレスは以下のURLである。

  • http://AAA.BBB.CCC.DDD:XXXX/コンテキストパス/ws/Webサービス名


Webサービスを、/wsにマッピングする設定は、「アプリケーションの設定」に記載しているので参照されたい。

5.3.2. How to use

本節では、SOAP Web Serviceの具体的な作成方法について説明する。
実装するアプリケーションは、Todoアプリケーションを例に説明を行う。

5.3.2.1. SOAPサーバの作成

5.3.2.1.1. プロジェクトの構成

各プロジェクトの依存関係

Jakarta XML Web Servicesを利用したSOAP Web Serviceの開発について」で述べたとおり、modelプロジェクトとwebserviceプロジェクトを追加する。
プロジェクトの追加方法は「SOAPサーバ用にプロジェクトの設定を変更する」に記載しているので、内容に沿って追加すること。
またそれに伴い、既存のプロジェクトに依存関係を追加することが必要となる。
Server Projects for SOAP

項番

プロジェクト名

説明

(1)
webプロジェクト
Webサービス実装クラスを配置する。
(2)
domainプロジェクト
WebServiceの実装クラスから呼び出されるServiceを配置する。
その他、Repositoryなどは従来と同じである。
(3)
webserviceプロジェクト
公開するWebServiceのインターフェースをここに配置する。
クライアントはこのインターフェースを使用してWebサービスを実行する。
(4)
modelプロジェクト
ドメイン層に属するクラスのうち、SOAP Web Serviceで使用するクラスのみをここに配置する。
クライアントからの入力値や返却結果はこのプロジェクト内のクラスを使用する。

5.3.2.1.2. アプリケーションの設定

Webサービスの公開について

APサーバとしてTomcatを利用する場合、Jakarta XML Web Services実装が存在しない。
そのため、本節で説明するSOAPサーバは、Jakarta XML Web Servicesの実装プロダクトとしてApache CXFを使用している。
また、Apache CXFを使用してWebサービスを公開するには、CXFServletの設定を追加する必要がある。

他のAPサーバーを利用する場合は、APサーバによってWebサービスの公開方法は異なるので、詳細は各APサーバーのマニュアルを参照されたい。

CXFServletを使用する設定

CXFServletを使用するため、pom.xmlにライブラリの設定を記述する。

[server projectName]-web/pom.xml

<!-- (1) -->
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-frontend-jaxws</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-transports-http</artifactId>
</dependency>
<!-- (2) -->
<dependency>
    <groupId>com.sun.xml.messaging.saaj</groupId>
    <artifactId>saaj-impl</artifactId>
</dependency>

項番

説明

(1)
CXFServletを使用するため、Apache CXFライブラリへの依存関係を追加する。

Note

cxf-rt-frontend-jaxwsとcxf-rt-transports-httpは、依存ライブラリのバージョンをBOMプロジェクトである terasoluna-dependencies で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。

(2)
CXFServletで使用されるSAAJライブラリへの依存関係を追加する。

Note

saaj-implのバージョンはterasoluna-dependenciesが依存しているSpring Bootで管理されているため、pom.xmlでのバージョンの指定は不要である。


次にweb.xmlにSOAP Web Serviceを受け付けるCXFServletを定義する。

[server projectName]-web/src/main/webapp/WEB-INF/web.xml

<!-- (1) -->
<servlet>
    <servlet-name>cxfServlet</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
    <init-param>
        <param-name>config-location</param-name>
        <param-value>classpath:/META-INF/spring/cxf-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<!-- (2) -->
<servlet-mapping>
    <servlet-name>cxfServlet</servlet-name>
    <url-pattern>/ws/*</url-pattern>
</servlet-mapping>

項番

説明

(1)
org.apache.cxf.transport.servlet.CXFServletのサーブレット定義を行う。
config-locationに設定するcxf-servlet.xmlは、作成したWebサービスを公開する際に必要な定義となる。
cxf-servlet.xmlの定義方法については、「WebService実装クラスの作成」にて説明を行う。
(2)
定義したサーブレットへのマッピングを定義する。この場合、コンテキスト名/ws配下にWebサービスが作成される。
このマッピング定義が、「SOAP Web Serviceとして公開されるURL」で説明していた設定となる。

パッケージのコンポーネントスキャン設定

Webサービスで使用するコンポーネントをスキャンするため、Javaで定義する場合は[server projectName]WsConfig.java、XMLで定義する場合は[server projectName]-ws.xmlを作成し、コンポーネントスキャンの定義を行い、Webサービスにインジェクションできるようにする。
  • [server projectName]-web/src/main/java/com/example/config/ws/[server projectName]WsConfig.java

    @Configuration
    @ComponentScan(basePackages = { "com.example.ws" })  // (1)
    public class ServerProjectNameWsConfig {
    }
    

    項番

    説明

    (1)
    Webサービスで使用するコンポーネントが格納されているパッケージを指定する。

web.xmlにConfiguration定義を追加する。

  • [server projectName]-web/src/main/webapp/WEB-INF/web.xml

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <!-- Root ApplicationContext -->
        <!-- (1) -->
        <param-value>
            com.example.config.app.ApplicationContextConfig
            com.example.config.web.SpringSecurityConfig
            com.example.config.ws.[server projectName]WsConfig
        </param-value>
    </context-param>
    

    項番

    説明

    (1)
    [server projectName]WsConfigをルートApplicationContext生成時の読み込み対象に加える。

入力チェックを行うための定義

入力チェックにはメソッドバリデーションを使用するため、以下の定義を追加する。
メソッドバリデーションにおける入力チェックルールの実装方法は 入力チェックの実装を参照されたい。
  • [server projectName]-web/src/main/java/com/example/config/app/ApplicationContextConfig.java

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor bean = new MethodValidationPostProcessor();
        bean.setValidator(validator());
        return bean;
    }
    
    @Bean("validator")
    public LocalValidatorFactoryBean validator() {
        return new LocalValidatorFactoryBean();
    }
    

5.3.2.1.3. Webサービスの実装

以下の作成を行う。

  • Domain Objectの作成

  • WebServiceインターフェイスの作成

  • WebService実装クラスの作成

Server Projects for SOAP

Domain Objectの作成

modelプロジェクト内に、Webサービスの引数や返り値に使用するDomain Objectを作成する。
java.io.Serializableインターフェースを実装した一般のJavaBeanと特に変わりはない。

[server projectName]-model/src/main/java/com/example/domain/model/Todo.java

package com.example.domain.model;

import java.io.Serializable;
import java.util.Date;

public class Todo implements Serializable {

    private String todoId;

    private String title;

    private String description;

    private boolean finished;

    private Date createdAt;

    // omitted setter and getter

}

WebServiceインターフェイスの作成

webserviceプロジェクト内にWebサービスを呼び出すインターフェースを作成する。

[server projectName]-webservice/src/main/java/com/example/ws/todo/TodoWebService.java

package com.example.ws.todo;

import java.util.List;

import jakarta.jws.WebMethod;
import jakarta.jws.WebParam;
import jakarta.jws.WebResult;
import jakarta.jws.WebService;

import com.example.domain.model.Todo;
import com.example.ws.webfault.WebFaultException;

@WebService(targetNamespace = "http://example.com/todo") // (1)
public interface TodoWebService {

    @WebMethod // (2)
    @WebResult(name = "todo") // (3)
    Todo getTodo(@WebParam(name = "todoId") /* (4) */ String todoId) throws WebFaultException;

}

項番

説明

(1)
@WebServiceを付けることで、WebServiceインターフェースであることを宣言する。
targetNamespace属性には、名前空間を定義するが、これは作成するWebサービスのパッケージ名と合わせることを推奨する。
詳細は、「パッケージ名とネームスペースについて」 を参照されたい。
targetNamespace属性の値は、@WebService付与クラスごとに設定する必要がある。そのため、ガイドライン上のソースを流用する場合は必ず変更すること。
targetNamespace属性の値はWSDL上に定義され、このWebサービスの名前空間を決定し、一意に特定するために使用される。
(2)
Webサービスのメソッドとして公開するメソッドに@WebMethodを付ける。
このアノテーションを付けることにより、WSDL上にメソッドが公開され、外部から使用することが可能になる。
(3)
返り値に@WebResultを付け、名前をname属性に指定する。返り値がない場合は不要。
このアノテーションを付けることにより、WSDL上に返り値として公開される。
(4)
引数に@WebParamを付け、名前をname属性に指定する。
このアノテーションを付けることにより、WSDL上に引数が公開され、外部から呼び出すときの必要なパラメータとして定義される。
WebFaultExceptionの詳細は「例外ハンドリングの実装」を参照されたい。

5.3.2.1.4. パッケージ名とネームスペースについて

パッケージ名および、ネームスペースの付け方について

パッケージ名が以下のような形式になっている場合
  • 【ドメイン】.【アプリケーション名(システム名)】.ws.【ユースケース名】

本ガイドラインでは、以下のようなネームスペースにすることを推奨する。
Webサービスをユースケース単位で分割する場合は、アプリケーション名の後にユースケース名を追加する。
他に分割する粒度があれば、その粒度でもよい。
  • http://【ドメイン】/【アプリケーション名(システム名)】/(【ユースケース名】)


ネームスペースとパッケージ名の関係

仕様ではないが、Namespaceとパッケージの命名について、XML Namespace Mapping(Red Hat JBoss Fuse)にまとまっている。
この命名規則に沿って例を示すが、ドメインをcom.example、アプリケーション名をtodoとした場合、以下のNamespaceとJavaのパッケージ名とするとよい。
Server and Client Projects for SOAP

5.3.2.1.5. WebService実装クラスの作成

webプロジェクト内にWebServiceインターフェースの実装クラスを作成する。

[server projectName]-web/src/main/java/com/example/ws/todo/TodoWebServiceImpl.java

package com.example.ws.todo;

import java.util.List;

import jakarta.jws.WebService;
import jakarta.xml.ws.BindingType;
import jakarta.xml.ws.soap.SOAPBinding;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;

import com.example.domain.model.Todo;
import com.example.domain.service.TodoService;
import com.example.ws.webfault.WebFaultException;
import com.example.ws.exception.WsExceptionHandler;
import com.example.ws.todo.TodoWebService;


@WebService(
        targetNamespace = "http://example.com/todo",
        serviceName = "TodoWebService",
        portName = "TodoWebPort",
        endpointInterface = "com.example.ws.todo.TodoWebService") // (1)
@BindingType(SOAPBinding.SOAP12HTTP_BINDING) // (2)
public class TodoWebServiceImpl extends SpringBeanAutowiringSupport implements TodoWebService { // (3)

    @Autowired // (4)
    TodoService todoService;

    @Override // (5)
    public Todo getTodo(String todoId) throws WebFaultException {
        return todoService.getTodo(todoId);
    }

}

項番

説明

(1)
@WebServiceを付けることで、WebServiceの実装クラスであることを宣言する。
targetNamespace属性は、WSDL上で使用されるネームスペース。
serviceName属性は、WSDL上のサービス名として公開される。
portName属性は、WSDL上のポート名として公開される。
endpointInterface属性は、このクラスが実装しているWebサービスのインターフェース名を定義する。

targetNamespaceserviceNameportNameの説明は、「WebServiceインターフェースの実装クラスとWSDLファイルの関係」を参照されたい。

Note

TodoWebServiceインターフェースでは、@WebServiceの属性としてportName属性, serviceName属性, endpointInterface属性を設定してはいけない。これは、このインターフェースはWSDL上のportType要素に対応しており、Webサービスの内容を記述する要素ではないためである。

(2)
@BindingTypeを付けることで、バインディングの方式を設定する。
SOAPBinding.SOAP12HTTP_BINDINGを定義するとSOAP1.2でのバインディングとなる。
(3)
先ほど作成したTodoWebServiceインターフェースを実装する。
org.springframework.web.context.support.SpringBeanAutowiringSupportを継承することで、SpringのBeanをDIできるようにする。
(4)
Serviceをインジェクションする。
通常のControllerでServiceを呼び出す場合と変わりはない。
(5)
Serviceを呼び出して業務処理を実行する。
通常のControllerでServiceを呼び出す場合と変わりはない。

Note

Webサービス関連のクラスはwsパッケージ配下にまとめることを推奨する。これは、アプリケーション層のクラスはappパッケージ配下に配置することを推奨しており、それらと区別をしやすくするためである。


WebService実装クラスを公開するために、CXFServlet用のBean定義ファイルに、SOAPのエンドポイントとなるクラス名およびアドレスを定義する。

[server projectName]-web/src/main/resources/META-INF/spring/cxf-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:soap="http://cxf.apache.org/bindings/soap"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
         https://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context
         https://www.springframework.org/schema/context/spring-context.xsd
         http://cxf.apache.org/jaxws
         http://cxf.apache.org/schemas/jaxws.xsd
         http://cxf.apache.org/bindings/soap
         http://cxf.apache.org/schemas/configuration/soap.xsd">
    <!-- (1) -->
    <jaxws:endpoint id="todoWebEndpoint" implementor="com.example.ws.todo.TodoWebServiceImpl"
        address="/TodoWebService" />

</beans>

項番

説明

(1)
org.apache.cxf.transport.servlet.CXFServlet内で動作するWebサービスのエンドポイントを定義する。

implementor属性に公開するWebサービスの実装クラスを指定する。
address属性にWebサービスを公開するアドレスを指定する。
アドレスは、公開するエンドポイントのパス部分のみ記述する。
属性の詳細についてはApache CXF JAX-WS Configurationを参照されたい。

5.3.2.1.6. 入力チェックの実装

SOAP Web Serviceにより送信されたパラメータの入力チェックには、Springから提供されているメソッドバリデーションを使用する。
メソッドバリデーションの詳細についてはMethod Validation対象のメソッドにするための定義方法を参照されたい。
以下のように、Serviceのインターフェースに入力チェック内容を定義する。

[server projectName]-domain/src/main/java/com/example/domain/service/todo/TodoService.java

package com.example.domain.service.todo;

import java.util.List;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.groups.Default;

import org.springframework.validation.annotation.Validated;

import com.example.domain.model.Todo;

@Validated // (1)
public interface TodoService {

    Todo getTodo(@NotEmpty String todoId); // (2)

    @Validated({ Default.class, Todo.Create.class }) // (3)
    Todo createTodo(@Valid Todo todo); // (4)

    @Validated({ Default.class, Todo.Update.class })
    Todo updateTodo(@Valid Todo todo);

}

項番

説明

(1)
@Validatedを付けることで、このインターフェースの実装クラスが入力チェック対象であることを宣言する。
(2)
引数をチェックする場合には、引数自体にアノテーションを付ける。
(3)
@Validatedにグループを指定し、特定の条件を絞って入力チェックすることも可能である。
入力チェックのグループ化については「バリデーションのグループ化」を参照されたい。
(4)
JavaBeanの入力チェックを行う場合も、引数に@Validを付ける。

[server projectName]-model/src/main/java/com/example/domain/model/Todo.java

package com.example.domain.model;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Null;
import java.io.Serializable;
import java.util.Date;

// (1)
public class Todo implements Serializable {

    // (2)
    public interface Create {
    }

    public interface Update {
    }

    @Null(groups = Create.class)
    @NotEmpty(groups = Update.class)
    private String todoId;

    @NotEmpty
    private String title;

    private String description;

    private boolean finished;

    @Null(groups = Create.class)
    private Date createdAt;

    // omitted setter and getter
}

項番

説明

(1)
Bean ValidationでJavaBeanの入力チェックを定義する。
詳細は「入力チェック」を参照されたい。
(2)
バリデーションのグループ化を行うために使用するインターフェースを定義する。

5.3.2.1.7. セキュリティ対策

認証処理

SOAPの認証・認可方式に関して、本ガイドラインではSpring SecurityでBasic認証を行う方法とServiceでの認可の方法のみ紹介する。
詳細な利用方法は、「認証」と「認可」を参照されたい。

以下にSOAP Web Serviceに対して、Basic認証を行うSpring Securityの設定例を示す。

  • [server projectName]-web/src/main/java/com/example/config/web/SpringSecurityConfig.java

    // (1)
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.securityMatcher(antMatcher("/ws/**"));
        http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        http.csrf(csrf -> csrf.disable());
        http.httpBasic(Customizer.withDefaults());
        http.authorizeHttpRequests(authz -> authz.requestMatchers(antMatcher("/**")).permitAll());
        return http.build();
    }
    
    // (2)
    @Bean
    public AuthenticationManager authenticationManager() {
        return new ProviderManager(authenticationProvider());
    }
    
    // (2)
    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(sampleUserDetailsService());
        authProvider.setPasswordEncoder(passwordEncoder);
        return authProvider;
    }
    

    項番

    説明

    (1)
    httpBasic(Customizer.withDefaults())を記述するとBasic認証を行うことができる。
    securityMatcherを使用して、Webサービスを実行する部分のみ認証を行う。
    (2)
    AuthenticationProviderを利用して、認証方式を定義する。
    実際の認証およびユーザ情報取得はUserDetailsServiceを作成して実施する必要がある。
    詳細は「認証」を参照されたい。

認可処理

認可はServiceごとにアノテーションを付けて行う。
詳細は「認可」のアクセス認可(Method)を参照されたい。
  • [server projectName]-web/src/main/java/com/example/config/web/SpringSecurityConfig.java

    @Configuration
    @EnableWebSecurity
    @EnableMethodSecurity  // (1)
    public class SpringSecurityConfig {
    

    項番

    説明

    (1)
    @EnableMethodSecurityアノテーションを付与する。

[server projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java

public class TodoServiceImpl implements TodoService {

    // omitted

    // (1)
    @PreAuthorize("isAuthenticated()")
    public List<Todo> getTodos() {
        // omitted
    }

    @PreAuthorize("hasRole('ROLE_ADMIN')")
    public Todo createTodo(Todo todo) {
        // omitted
    }

}

項番

説明

(1)
認可処理を行うメソッドにorg.springframework.security.access.prepost.PreAuthorizeアノテーションを設定する。

CSRF対策の無効化

SOAP Web Serviceはセッションを利用せず、ステートレスな通信にすべきである。
しかし、ブランクプロジェクトのデフォルトの設定では、CSRF対策が有効化されている。
そのため、以下の設定を追加し、SOAP Web Serviceのリクエストに対して、CSRF対策の処理が行われないようにする。
  • [server projectName]-web/src/main/java/com/example/config/web/SpringSecurityConfig.java

    // (1)
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.securityMatcher(antMatcher("/ws/**"));
        http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        http.csrf(csrf -> csrf.disable());
        http.httpBasic(Customizer.withDefaults());
        http.authorizeHttpRequests(authz -> authz.requestMatchers(antMatcher("/**")).permitAll());
        return http.build();
    }
    

    項番

    説明

    (1)
    SOAP Web Service用のSpring Securityの定義を追加する。
    securityMatcherにSOAP Web Service用のリクエストパスのURLパターンを指定する。
    このコード例では、/ws/で始まるリクエストパスをSOAP Web Service用のリクエストパスとしている。
    また、sessionManagementにセッション作成ポリシーとしてSessionManagementConfigurer#sessionCreationPolicy(SessionCreationPolicy.STATELESS)を指定する事で、
    Spring Securityの処理でセッションが使用されなくなる。

    CSRF対策を無効化するために、csrfCsrfConfigurer#disableを指定する。

5.3.2.1.8. 例外ハンドリングの実装

Jakarta XML Web Servicesの仕様となるが、SOAPサーバで例外が発生した場合に、WebFaultアノテーションを付与した専用の例外クラスをスローすることにより、SOAPのエラー電文としてクライアントに返却することができる。
本ガンドラインでも上記例外の機能を利用しているが、詳細なエラー情報を送るために独自の例外ハンドリングの仕組みを追加で実装している。

SOAPサーバにおける例外処理の実装には、以下のことが必要である。
  1. WebFaultExceptionクラスとその関連クラスの実装

  2. サーバで発生する例外を、WebFaultExceptionでラップする例外ハンドラの作成

  3. 例外発生箇所での例外ハンドラの呼び出し

以降ではこれらの具体的方法を説明する。


WebFaultExceptionクラスとその関連クラスの実装

SOAPサーバで発生した例外はこれから記述する例外を実装したクラス(SOAPFault)を使用することで、クライアントへの通知メッセージを決定することができる。

具体的には以下のクラスを作成する。

項番

クラス名

概要

(1)
ErrorBean
発生した例外のコードとメッセージなどを保持するクラス。
(2)
WebFaultType
例外の種類を判別するために使用する列挙型。
(3)
WebFaultBean
ErrorBeanWebFaultTypeを保持するクラス。ErrorBeanListで保持して例外情報を複数保持できる。
(4)
WebFaultException
WebFaultBeanを保持する例外クラス。@WebFaultを付けて、SOAPFaultであることを宣言する。

これらの例外はSOAPサーバ、クライアントで共用するため、[server projectName]-webserviceに配置する。


[server projectName]-webservice/src/main/java/com/example/ws/webfault/ErrorBean.java

package com.example.ws.webfault;

public class ErrorBean { // (1)
    private String code;
    private String message;
    private String path;

    // omitted setter and getter
}

項番

説明

(1)
例外のメッセージなどを保持するクラスを作成する。

[server projectName]-webservice/src/main/java/com/example/ws/webfault/WebFaultType.java

package com.example.ws.webfault;

public enum WebFaultType { // (2)
    AccessDeniedFault,
    BusinessFault,
    ResourceNotFoundFault,
    ValidationFault,
}

項番

説明

(1)
例外の種類を判別するために使用する列挙型を定義する。

[server projectName]-webservice/src/main/java/com/example/ws/webfault/WebFaultBean.java

package com.example.ws.webfault;

import java.util.ArrayList;
import java.util.List;

public class WebFaultBean { // (3)

    private WebFaultType type;

    private List<ErrorBean> errors = new ArrayList<ErrorBean>();

    public WebFaultBean(WebFaultType type) {
        this.type = type;
    }

    public void addError(String code, String message) {
        addError(code, message, null);
    }

    public void addError(String code, String message, String path) {
        errors.add(new ErrorBean(code, message, path));
    }

    // omitted setter and getter
}

項番

説明

(1)
ErrorBeanWebFaultTypeを保持するクラスを作成する。

[server projectName]-webservice/src/main/java/com/example/ws/webfault/WebFaultException.java

package com.example.ws.webfault;

import java.util.List;

import jakarta.xml.ws.WebFault;

@WebFault(name = "WebFault", targetNamespace = "http://example.com/todo") // (1)
public class WebFaultException extends Exception { // (2)
    private WebFaultBean faultInfo; // (3)

    public WebFaultException() {
    }

    public WebFaultException(String message, WebFaultBean faultInfo) {
        super(message);
        this.faultInfo = faultInfo;
    }

    public WebFaultException(String message, WebFaultBean faultInfo, Throwable e) {
        super(message, e);
        this.faultInfo = faultInfo;
    }

    public List<ErrorBean> getErrors() {
        return this.faultInfo.getErrors();
    }

    public WebFaultType getType() {
        return this.faultInfo.getType();
    }
    // omitted setter and getter
}

項番

説明

(1)
Exception継承クラスに@WebFaultを付けて、SOAPFaultであることを宣言する。
name属性には、クライアントに送信するSOAPFaultのname属性を設定する。
targetNamespace属性には、使用するネームスペースを設定する。Webサービスと同じにする必要がある。
(2)
ここで、WebFaultExceptionの親クラスをRuntimeExceptionではなくExceptionとしているのは、Jakarta XML Web Services 4.0 -3.7. Service Specific Exceptionにおいて、
java.lang.RuntimeExceptionsjava.rmi.RemoteExceptionsを含むサブクラスは、WebServiceで取り扱う例外として利用してはならないこと、WSDL自体にもマッピングしてはならないことの2点が禁止事項として記載されている事による。
実際にWebFaultExceptionの親クラスをRuntimeExceptionとした場合、APサーバのJakarta XML Web Servicesに実装依存するが、クライアントで@WebFaultを付けた例外クラス(WebFaultException)を取得することができず、エラーの種類やメッセージを取得することができなくなる。
(3)
faultInfoをフィールドに保持させるとともに、コード例のように以下のようなコンストラクタとメソッドを持たせる。
  • メッセージ文字列とfaultInfoを引数とするコンストラクタ

  • メッセージ文字列とfaultInfoと原因例外を引数とするコンストラクタ

  • getFaultInfoメソッド

Note

WebFaultExceptionのコンストラクタとフィールドについて

WebFaultExceptionには、デフォルトコンストラクタと各フィールドに対応するsetterが必須となる。これは、クライアントの内部処理で、WebFaultExceptionを作成する際に使用するためである。


サーバで発生する例外を、WebFaultExceptionでラップする例外ハンドラの作成

Serviceから発生する実行時例外をSOAPFaultでラップするために例外ハンドラークラスを作成する。 本ガイドラインではWebService実装クラスがこのハンドラーを用いて例外を変換してスローする方針とする。

Serviceからスローされる例外は以下を想定している。必要に応じて追加されたい。

例外名

内容

org.springframework.security.access.AccessDeniedException
認可エラー時の例外
jakarta.validation.ConstraintViolationException
入力チェックエラー時の例外
org.terasoluna.gfw.common.exception.ResourceNotFoundException
リソースが見つからない場合の例外
org.terasoluna.gfw.common.exception.BusinessException
業務例外

[server projectName]-web/src/main/java/com/example/ws/exception/WsExceptionHandler.java

package com.example.ws.exception;

import java.util.Iterator;
import java.util.Locale;
import java.util.Set;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Path;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.exception.ExceptionCodeResolver;
import org.terasoluna.gfw.common.exception.ExceptionLogger;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import org.terasoluna.gfw.common.exception.SystemException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;

import com.example.ws.webfault.WebFaultBean;
import com.example.ws.webfault.WebFaultException;
import com.example.ws.webfault.WebFaultType;

@Component  // (1)
public class WsExceptionHandler {

    @Autowired
    MessageSource messageSource; // (2)

    @Autowired
    ExceptionCodeResolver exceptionCodeResolver; // (3)

    @Autowired
    ExceptionLogger exceptionLogger; // (4)

    // (5)
    public void translateException(Exception e) throws WebFaultException {
        loggingException(e);
        WebFaultBean faultInfo = null;

        if (e instanceof AccessDeniedException) {
            faultInfo = new WebFaultBean(WebFaultType.AccessDeniedFault);
            faultInfo.addError(e.getClass().getName(), e.getMessage());
        } else if (e instanceof ConstraintViolationException) {
            faultInfo = new WebFaultBean(WebFaultType.ValidationFault);
            this.addErrors(faultInfo, ((ConstraintViolationException) e).getConstraintViolations());
        } else if (e instanceof ResourceNotFoundException) {
            faultInfo = new WebFaultBean(WebFaultType.ResourceNotFoundFault);
            this.addErrors(faultInfo, ((ResourceNotFoundException) e).getResultMessages());
        } else if (e instanceof BusinessException) {
            faultInfo = new WebFaultBean(WebFaultType.BusinessFault);
            this.addErrors(faultInfo, ((BusinessException) e).getResultMessages());
        } else {
            // not translate.
            throw new SystemException("e.xx.fw.9001", e);
        }

        throw new WebFaultException(e.getMessage(), faultInfo, e.getCause());
    }

    private void loggingException(Exception e) {
        exceptionLogger.log(e);
    }

    private void addErrors(WebFaultBean faultInfo, Set<ConstraintViolation<?>> constraintViolations) {
        for (ConstraintViolation<?> v : constraintViolations) {
            Iterator<Path.Node> pathIt = v.getPropertyPath().iterator();
            pathIt.next(); // method name node (skip)
            Path.Node methodArgumentNameNode = pathIt.next();
            faultInfo.addError(
                v.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName(),
                v.getMessage(),
                pathIt.hasNext() ? pathIt.next().toString() : methodArgumentNameNode.toString());
        }

    }

    private void addErrors(WebFaultBean faultInfo, ResultMessages resultMessages) {
        Locale locale = Locale.getDefault();
        for (ResultMessage message : resultMessages) {
            faultInfo.addError(
                message.getCode(),
                messageSource.getMessage(message.getCode(), message.getArgs(), message.getText(), locale));
        }
    }

}

項番

説明

(1)
本クラスをDIコンテナに管理をさせるため、@Componentを付ける。
(2)
出力するメッセージを取得するためにMessageSourceを使用する。
(3)
共通ライブラリが提供するExceptionCodeResolverMessageSourceを使用して例外の種類と例外コードをマッピングする。
詳細は、「例外ハンドリング」を参照されたい。
(4)
共通ライブラリが提供するExceptionLoggerを使用して例外情報を例外に出力する。
詳細は、「例外ハンドリング」を参照されたい。
(5)
Serviceから発生しうる各例外について、SOAPFaultへのラップを行う。
例外のマッピングは冒頭の表を参考されたい。

Note

その他の例外の扱いについて

その他の例外発生時(上記のtranslateExceptionメソッドのelse部分)では、クライアントでは詳細な例外の内容は通知されず、jakarta.xml.ws.soap.SOAPFaultExceptionが発生するのみとなる。他の例外同様にラップしてクライアント側に通知することも可能である。


例外発生箇所での例外ハンドラの呼び出し

Webサービスクラスにて、例外ハンドラーを呼び出す。以下はその例である。

[server projectName]-web/src/main/java/com/example/ws/todo/TodoWebServiceImpl.java

@WebService(
        targetNamespace = "http://example.com/todo",
        serviceName = "TodoWebService",
        portName = "TodoWebPort",
        endpointInterface = "com.example.ws.todo.TodoWebService")
@BindingType(SOAPBinding.SOAP12HTTP_BINDING)
public class TodoWebServiceImpl extends SpringBeanAutowiringSupport implements TodoWebService {
    @Autowired
    TodoService todoService;
    @Autowired
    WsExceptionHandler handler; // (1)

    @Override
    public Todo getTodo(String todoId) throws WebFaultException /* (2) */ {
        try {
            return todoService.getTodo(todoId);
        } catch (RuntimeException e) {
            handler.translateException(e); // (3)
        }
    }
}

項番

説明

(1)
例外ハンドラーをインジェクションする。
(2)
WebFaultExceptionにラップしてスローするため、throws句を付ける。
(3)
実行時例外が発生した場合は、例外ハンドラークラスに処理を委譲する。

5.3.2.1.9. MTOMを利用した大容量のバイナリデータを扱う方法

SOAPでは、バイナリデータを扱う場合、Byte配列にマッピングすることで、送受信を行うことができる。
ただし、大容量のバイナリデータを扱う場合、ヒープが枯渇するなどの問題が発生することがある。
上記問題の発生を防ぐため、MTOM(Message Transmission Optimization Mechanism)に準拠した実装を行うことで、最適化した状態で添付ファイルとしてバイナリデータを扱うことができる。
詳細な定義は W3C -SOAP Message Transmission Optimization Mechanism-を参照されたい。
以下にその方法を記述する。

[server projectName]-webservice/src/main/java/com/example/ws/todo/TodoWebService.java

package com.example.ws.todo;

import java.util.List;

import jakarta.activation.DataHandler;
import jakarta.jws.WebMethod;
import jakarta.jws.WebParam;
import jakarta.jws.WebResult;
import jakarta.jws.WebService;
import jakarta.xml.bind.annotation.XmlMimeType;

import com.example.domain.model.Todo;
import com.example.ws.webfault.WebFaultException;

@WebService(targetNamespace = "http://example.com/todo")
public interface TodoWebService {

    // omitted

    @WebMethod
    void uploadFile(@XmlMimeType("application/octet-stream") /* (1) */ DataHandler dataHandler) throws WebFaultException;

}

項番

説明

(1)
バイナリデータを処理するjakarta.activation.DataHandlerに対して@XmlMimeTypeを付ける。

[server projectName]-web/src/main/java/com/example/ws/todo/TodoWebServiceImpl.java

package com.example.ws.todo;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import jakarta.activation.DataHandler;
import jakarta.jws.WebService;
import jakarta.xml.ws.BindingType;
import jakarta.xml.ws.soap.MTOM;
import jakarta.xml.ws.soap.SOAPBinding;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import org.terasoluna.gfw.common.exception.SystemException;

import com.example.domain.model.Todo;
import com.example.domain.service.TodoService;
import com.example.ws.webfault.WebFaultException;
import com.example.ws.exception.WsExceptionHandler;

// (1)
@MTOM
@WebService(
        targetNamespace = "http://example.com/todo",
        serviceName = "TodoWebService",
        portName = "TodoWebPort",
        endpointInterface = "com.example.ws.todo.TodoWebService")
@BindingType(SOAPBinding.SOAP12HTTP_BINDING)
public class TodoWebServiceImpl extends SpringBeanAutowiringSupport implements TodoWebService {

    @Autowired
    TodoService todoService;

    // omitted

    @Override
    public void uploadFile(DataHandler dataHandler) throws WebFaultException {

        try (InputStream inputStream = dataHandler.getInputStream()){ // (2)
            todoService.uploadFile(inputStream);
        } catch (Exception e) {
            handler.translateException(e);
        }
    }

}

項番

説明

(1)
@MTOMを付けて、MTOMに準拠した実装を使用することを宣言する。
(2)
jakarta.activation.DataHandlerからjava.io.InputStreamを取得してファイルを扱う。

5.3.2.2. クライアントの作成

5.3.2.2.1. プロジェクトの構成

Jakarta XML Web Servicesを利用したSOAP Web Serviceの開発について」で述べたとおり、modelプロジェクトとwebserviceプロジェクトをSOAPサーバから受領する前提である。

Client Projects for SOAP

項番

プロジェクト名

説明

(1)
webプロジェクト
Controllerを作成する。
通常の画面遷移時のControllerと特に変更点はない。
(2)
domainプロジェクト
Serviceクラスからwebserviceプロジェクトで用意されたWebServeインターフェースを使用してWebサービスを呼び出す。
SOAPサーバと通信する際に使用するWebServiceインターフェースを実装したプロキシを定義する。
(3)
webserviceプロジェクト
SOAPサーバと同じ資材を配置する。
クライアントはこのインターフェースを使用してWebサービスを実行する。
(4)
modelプロジェクト
SOAPサーバと同じ資材を配置する。
SOAPサーバに渡す入力値や返却結果はこのプロジェクト内のクラスを使用する。
(5)
envプロジェクト
domainプロジェクトで定義したプロキシの環境依存する値を定義する。
プロキシの定義から環境依存する値をプロパティファイルに集約し、プロパティファイルのみenvプロジェクトに配置する。

Note

プロキシの定義ついて

試験用SOAPサーバ、本番用SOAPサーバ等、複数環境向けのプロキシを定義する際に発生する重複部分を排除し、管理を容易にするために、当ガイドラインではプロキシの定義はdomainプロジェクトで行い、環境依存する値はプロパティファイルに集約、プロパティファイルのみenvプロジェクトに配置することを推奨する。

ユニットテストでプロキシのスタブやモックを使用する場合は、ユニットテスト用のコンポーネントを定義するためのBean定義ファイル(test-context.xml)にBeanを定義する。


5.3.2.2.2. Webサービス クライアントの実装

以下のクラスの実装を行う。

  • WebServiceインターフェースを実装したプロキシの定義

  • ServiceクラスからWebServiceインターフェース経由でWebサービスを呼び出す。

Server Projects for SOAP

WebServiceインターフェースを実装したプロキシの作成

WebServiceインターフェースを実装したプロキシの定義を行う。

サービス名をもとに、jakarta.xml.ws.Serviceを生成する。
そして、生成したjakarta.xml.ws.ServiceからWebサービスのポートを特定し、そのポートを実装したプロキシを生成する。
以下はその設定例となる。
サービスやポートの説明は、「WebServiceインターフェースの実装クラスとWSDLファイルの関係」を参照されたい。
テスト等でWSDLファイルに記載されているエンドポイントアドレス以外を指定したい場合の設定については、「テスト等でエンドポイントアドレスを上書き指定したい場合」を参照されたい。
  • [client projectName]-domain/src/main/java/com/example/config/app/[client projectName]DomainConfig.java

    // (3)
    @Value("${webservice.todoWebService.wsdlDocumentResource}")
    private String wsdlDocumentResource;
    
    // (1)
    @Bean("wsService")
    public Service wsService() throws MalformedURLException {
        QName serviceName = new QName("http://example.com/todo", "TodoWebService");  // (2)
        return Service.create(new URL(wsdlDocumentResource), serviceName);  // (3)
    }
    
    // (4)
    @Bean("todoWebService")
    public TodoWebService todoWebService() throws MalformedURLException {
        QName portName = new QName("http://example.com/todo", "TodoWebPort");  // (5)
        return wsService().getPort(portName, TodoWebService.class);  // (6)
    }
    
  • [client projectName]-env/src/main/resources/META-INF/spring/[client projectName]-infra.properties

    # (7)
    webservice.todoWebService.wsdlDocumentResource=classpath:META-INF/spring/soap/todoWebService.wsdl
    

    項番

    説明

    (1)
    jakarta.xml.ws.Serviceの定義を行う。
    (2)
    WSDLファイルからサービスを特定するために、javax.xml.namespace.QNameを定義する。
    引数には、SOAPサーバーで公開しているWEBサービスのNamespace、サービス名を指定する。
    (3)
    jakarta.xml.ws.Serviceの生成を行う。
    引数には、SOAPサーバーで公開しているWSDLのURLかファイルパス、サービスを特定するために生成したjavax.xml.namespace.QNameを指定する。
    WSDLのパスは、後述するプロパティファイルから取得する。
    (4)
    WebServiceの動的プロキシの定義を行う。
    (5)
    WSDLファイルからポートを特定するために、javax.xml.namespace.QNameを定義する。
    引数には、SOAPサーバーで公開しているWEBサービスのNamespace、ポート名を指定する。
    (6)
    jakarta.xml.ws.Serviceからポートの動的プロキシを取得する。
    引数には、ポートを特定するために生成したjavax.xml.namespace.QName、WebServiceのインターフェースを指定する。
    (7)
    WSDLのパスを設定する。

    WSDLファイルをクライアントに配備し、プロパティファイルにWSDLのファイルパスを指定することを推奨する。
    URLを指定してしまうと、Bean生成時にSOAPサーバに対してWSDLファイルを取得しに行ってしまい、SOAPサーバが起動していない場合エラーとなり、クライアントアプリケーションが起動できなくなってしまう。
    上記対応を行うと、SOAPサーバが起動していない場合でもクライアントアプリケーションを起動させることができる。

ServiceからWebサービスを呼び出す

上記で作成したWebサービスをServiceでインジェクションして実行する。

[client projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java

package com.example.soap.domain.service.todo;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.domain.model.Todo;
import com.example.ws.webfault.WebFaultException;
import com.example.ws.todo.TodoWebService;

@Service
public class TodoServiceImpl implements TodoService {

    @Autowired
    TodoWebService todoWebService;

    @Override
    public void createTodo(Todo todo) {
        // (1)
        try {
            todoWebService.createTodo(todo);
        } catch (WebFaultException e) {
            // (2)
            // handle exception…
        }
    }
}

項番

説明

(1)
TodoWebServiceをインジェクションして、実行対象のServiceを呼び出す。
(2)
サーバ側で、例外が発生した場合は、WebFaultExceptionにラップされて送信される。
内容に応じて処理を行う。
例外処理の詳細は「例外ハンドリングの実装」を参照されたい。

Note

レスポンスの情報取得

リトライを考慮するなど、レスポンスの情報をクライアントで取得可能な場合、以下のようにjakarta.xml.ws.BindingProviderクラスにキャストすることで取得できる。

BindingProvider provider = (BindingProvider) todoWebService;
int status = (int) provider.getResponseContext().get(MessageContext.HTTP_RESPONSE_CODE);

5.3.2.2.3. セキュリティ対策

認証処理

jakarta.xml.ws.Serviceを使用して、Basic認証を使用しているSOAPサーバと通信をする際には、jakarta.xml.ws.BindingProviderで取得できるRequestContextにユーザーとパスワードを設定することで認証を行うことができる。
設定の方法は以下の通りである。

jakarta.xml.ws.Serviceの定義が複雑になるため、FactoryBeanを使用してBean定義を行う。

[client projectName]-domain/src/main/java/com/example/domain/service/todo/factory/TodoWebServiceFactoryBean.java

public class TodoWebServiceFactoryBean implements
                                        FactoryBean<TodoWebService> {

    private String wsdlDocumentResource;

    private String userName;

    private String password;

    @Override
    public TodoWebService getObject() throws Exception {
        QName serviceName = new QName("http://example.com/todo", "TodoWebService");
        QName portName = new QName("http://example.com/todo", "TodoWebPort");

        Service service = Service.create(new URL(wsdlDocumentResource), serviceName);
        TodoWebService todoWebService = service.getPort(portName, TodoWebService.class);

        BindingProvider bindingProvider = (BindingProvider) todoWebService;
        Map<String, Object> requestContext = bindingProvider.getRequestContext();
        // (1)
        requestContext.put(BindingProvider.USERNAME_PROPERTY, userName);
        requestContext.put(BindingProvider.PASSWORD_PROPERTY, password);

        return todoWebService;
    }

    public void setWsdlDocumentResource(String wsdlDocumentResource) {
        this.wsdlDocumentResource = wsdlDocumentResource;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public Class<?> getObjectType() {
        return TodoWebService.class;
    }
}

項番

説明

(1)
取得したRequestContextに対して、Basic認証で使用するユーザー名、パスワードの設定を追加する。
ユーザー名のキーにBindingProvider.USERNAME_PROPERTYを設定し、値にはユーザー名を設定する。
パスワードのキーにBindingProvider.PASSWORD_PROPERTYを設定し、値にはパスワードを設定する。

FactoryBeanのBean定義を行う。

  • [client projectName]-domain/src/main/java/com/example/config/app/[client projectName]DomainConfig.java

    @Value("${webservice.todoWebService.wsdlDocumentResource}")
    private String wsdlDocumentResource;
    
    @Value("${webservice.todoWebService.username}")  // (1)
    private String username;
    
    @Value("${webservice.todoWebService.password}")  // (1)
    private String password;
    
    @Bean("todoWebService")
    public TodoWebServiceFactoryBean todoWebService() {
        TodoWebServiceFactoryBean factory = new TodoWebServiceFactoryBean();
        factory.setWsdlDocumentResource(wsdlDocumentResource);
        // (1)
        factory.setUserName(username);
        factory.setPassword(password);
        return factory;
    }
    
  • [client projectName]-env/src/main/resources/META-INF/spring/[client projectName]-infra.properties

    # (2)
    webservice.todoWebService.username=testuser
    webservice.todoWebService.password=password
    

    項番

    説明

    (1)
    後述するプロパティファイルからユーザー名、パスワードの値を取得し、FactoryBeanに設定する。
    (2)
    認証に使用するユーザ名とパスワードを定義する。

5.3.2.2.4. WEBサービス クライアントで取り扱う例外の種類

WEBサービス クライアントからSOAPサーバーに通信を行う際に、発生する例外は以下の通りとなる。
以下で定義した例外は、必要に応じてクライアントでハンドリングを行う必要がある例外となる。

例外名

内容

jakarta.xml.ws.WebServiceException
WEBサービス全般で利用される実行時例外であり、SOAPサーバーとの通信時の障害、設定の不備等の広域の例外を表現する。
jakarta.xml.ws.soap.SOAPFaultException
SOAP固有のエラー情報を保持した例外であり、SOAPサーバにて予期せぬ例外が発生した場合等に、Jakarta XML Web Servicesにより生成される例外。
例外ハンドリングの実装」にてハンドリングできなかった例外が本例外に該当する。
com.example.ws.webfault.WebFaultException
SOAPサーバの「例外ハンドリングの実装」にてラップした例外。
主に、業務ロジックにて検知可能な例外が本例外に該当する。

5.3.2.2.5. 例外ハンドリングの実装

SOAPサーバでは、WebFaultExceptionに例外をラップして、スローすることを推奨している。
クライアントはWebFaultExceptionをキャッチして、その原因例外を判定してそれぞれの処理を行う。
以下の例では、WebServiceExceptionSOAPFaultExceptionをシステム例外扱いとしているためクライアントでのハンドリングは行っていない。
WebServiceExceptionSOAPFaultExceptionに関してもクライアントで例外処理を追加したい場合は、ハンドリングする必要がある。
@Override
public void createTodo(Todo todo) {

    try {
        // (1)
        todoWebService.createTodo(todo);
    } catch (WebFaultException e) {
        // (2)
        switch (e.getFaultInfo().getType()) {
        case ValidationFault:
            // handle exception…
            break;
        case BusinessFault:
            // handle exception…
            break;
        default:
            // handle exception…
            break;
        }
    }

}

項番

説明

(1)
Webサービスを呼び出す。throwsがついているため、WebFaultExceptionをキャッチする必要がある。
(2)
faultInfoの種別で例外を判定し、それぞれの処理を記述する(画面にメッセージを出す、例外をスローするなど)

5.3.2.2.6. タイムアウトの設定

クライアントで指定できるタイムアウトは大きく以下の2つがある。

  • SOAPサーバとのコネクションタイムアウト

  • SOAPサーバへのリクエストタイムアウト

jakarta.xml.ws.Serviceを使用して、タイムアウトの設定をするには、jakarta.xml.ws.BindingProviderで取得できるRequestContextにタイムアウト値を設定する必要がある。
設定の方法は以下の通りである。

jakarta.xml.ws.Serviceの定義が複雑になるため、FactoryBeanを使用してBean定義を行う。

[client projectName]-domain/src/main/java/com/example/domain/service/todo/factory/TodoWebServiceFactoryBean.java

public class TodoWebServiceFactoryBean implements
                                            FactoryBean<TodoWebService> {

    private String wsdlDocumentResource;

    private long connectionTimeout;

    private long requestTimeout;

    @Override
    public TodoWebService getObject() throws Exception {
        QName serviceName = new QName("http://example.com/todo", "TodoWebService");
        QName portName = new QName("http://example.com/todo", "TodoWebPort");

        Service service = Service.create(new URL(wsdlDocumentResource), serviceName);
        TodoWebService todoWebService = service.getPort(portName, TodoWebService.class);

        BindingProvider bindingProvider = (BindingProvider) todoWebService;
        Map<String, Object> requestContext = bindingProvider.getRequestContext();
        // (1)
        requestContext.put("jakarta.xml.ws.client.connectionTimeout", connectionTimeout);
        requestContext.put("jakarta.xml.ws.client.receiveTimeout", requestTimeout);

        return todoWebService;
    }

    public void setWsdlDocumentResource(String wsdlDocumentResource) {
        this.wsdlDocumentResource = wsdlDocumentResource;
    }

    public void setConnectionTimeout(long connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public void setRequestTimeout(long requestTimeout) {
        this.requestTimeout = requestTimeout;
    }

    @Override
    public Class<?> getObjectType() {
        return TodoWebService.class;
    }
}

項番

説明

(1)
取得したRequestContextに対して、タイムアウトの設定を追加する。
コネクションタイムアウトのキーにjakarta.xml.ws.client.connectionTimeoutを設定し、値にはコネクションタイムアウト値を設定する。
リクエストタイムアウトのキーにjakarta.xml.ws.client.receiveTimeoutを設定し、値にはリクエストタイムアウト値を設定する。

Note

タイムアウト定義に使用するキーについて

それぞれのタイムアウトを定義するキーはJakarta XML Web Servicesの実装により異なる値を設定する必要がある。 詳細はJAX_WS-1166 Standardize timeout settingsを参照されたい。

なお、Apache CXF 3.x までは、タイムアウトのキーとしてjavax.xml.ws.client.connectionTimeoutjavax.xml.ws.client.receiveTimeoutを利用していたが、Apache CXF 4.x 以降は、jakarta.xml.ws.client.connectionTimeoutjakarta.xml.ws.client.receiveTimeoutにキーが変更となっている。


FactoryBeanのBean定義を行う。

  • [client projectName]-domain/src/main/java/com/example/config/app/[client projectName]DomainConfig.java

    @Value("${webservice.todoWebService.wsdlDocumentResource}")
    private String wsdlDocumentResource;
    
    @Value("${webservice.connect.timeout}")  // (1)
    private long connectionTimeout;
    
    @Value("${webservice.request.timeout}")  // (1)
    private long requestTimeout;
    
    @Bean("todoWebService")
    public TodoWebServiceFactoryBean todoWebService() {
        TodoWebServiceFactoryBean factory = new TodoWebServiceFactoryBean();
        factory.setWsdlDocumentResource(wsdlDocumentResource);
        // (1)
        factory.setConnectionTimeout(connectionTimeout);
        factory.setRequestTimeout(requestTimeout);
        return factory;
    }
    
  • [client projectName]-env/src/main/resources/META-INF/spring/[client projectName]-infra.properties

    # (2)
    webservice.connect.timeout=3000
    webservice.request.timeout=3000
    

    項番

    説明

    (1)
    後述するプロパティファイルからコネクションタイムアウト、リクエストタイムアウトの値を取得し、FactoryBeanに設定する。
    (2)
    コネクションタイムアウト値、リクエストタイムアウト値を定義する。

5.3.3. Appendix

5.3.3.1. SOAPサーバ用にプロジェクトの設定を変更する

SOAPサーバを作成する場合、ブランクプロジェクトにmodelプロジェクトとwebserviceプロジェクトを追加することを推奨する。
以下にその方法を記述する。
ブランクプロジェクトの初期状態は以下の構成になっている。
なお、artifactIdにはブランクプロジェクト作成時に指定したartifactIdが設定される。
artifactId
├── pom.xml
├── artifactId-domain
├── artifactId-env
├── artifactId-initdb
├── artifactId-selenium
└── artifactId-web

以下のようなプロジェクト構成にする。

artifactId
├── pom.xml
├── artifactId-domain
├── artifactId-env
├── artifactId-initdb
├── artifactId-selenium
├── artifactId-web
├── artifactId-model
└── artifactId-webservice

5.3.3.1.1. 既存プロジェクトの変更

ブランクプロジェクトの初期状態では、ControllerなどWebアプリケーションの簡易実装が含まれている。
そのままにしてもSOAP Web Serviceは実現可能だが、不要であるため、削除することを推奨する。

5.3.3.1.2. modelプロジェクトの作成

modelプロジェクトの構成について説明する。

artifactId-model
    ├── pom.xml  ... (1)
項番
説明
(1)

modelモジュールの構成を定義するPOM(Project Object Model)ファイル。 このファイルでは、以下の定義を行う。

  • 依存ライブラリとビルド用プラグインの定義

  • jarファイルを作成するための定義

pom.xmlは以下のようなイメージになる。必要に応じて編集する必要がある。
実際には、「artifactId」と「groupId」はブランクプロジェクト作成時に指定した値を設定する必要がある。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <artifactId>artifactId-model</artifactId>
    <packaging>jar</packaging>
    <parent>
        <groupId>groupId</groupId>
        <artifactId>artifactId</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <dependencies>
        <!-- == Begin TERASOLUNA == -->
        <dependency>
            <groupId>org.terasoluna.gfw</groupId>
            <artifactId>terasoluna-gfw-common-dependencies</artifactId>
            <type>pom</type>
        </dependency>
        <!-- == End TERASOLUNA == -->
    </dependencies>
</project>

5.3.3.1.3. webserviceプロジェクトの作成

webserviceプロジェクトの構成について説明する。

artifactId-webservice
    ├── pom.xml  ... (1)
項番
説明
(1)

webserviceモジュールの構成を定義するPOM(Project Object Model)ファイル。 このファイルでは、以下の定義を行う。

  • 依存ライブラリとビルド用プラグインの定義

  • jarファイルを作成するための定義

pom.xmlは以下のようなイメージになる。必要に応じて編集する必要がある。
実際には、「artifactId」と「groupId」はブランクプロジェクト作成時に指定した値を設定する必要がある。
なお、webserviceプロジェクトのコンパイルにはJakarta XML Web Services APIが必要となるため、jakarta.xml.ws-apiを依存関係に追加する必要がある。
追加するjakarta.xml.ws-apiのバージョンに関しては、Apache CXFが使用するJakarta XML Web Services APIのバージョンに合わせて設定する必要があるため、「アプリケーションの設定」に記載されているApache CXFのバージョンに合わせて設定を行う場合は、以下のpom.xmlに記載されたバージョンを指定する必要がある。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <artifactId>artifactId-webservice</artifactId>
    <packaging>jar</packaging>
    <parent>
        <groupId>groupId</groupId>
        <artifactId>artifactId</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>artifactId-model</artifactId>
        </dependency>

        <!-- == Begin TERASOLUNA == -->
        <dependency>
            <groupId>org.terasoluna.gfw</groupId>
            <artifactId>terasoluna-gfw-security-core-dependencies</artifactId>
            <type>pom</type>
        </dependency>
        <!-- == End TERASOLUNA == -->

        <!-- == Start Jakarta XML Web Services  == -->
        <dependency>
            <groupId>jakarta.xml.ws</groupId>
            <artifactId>jakarta.xml.ws-api</artifactId>
            <version>4.0.2</version>
        </dependency>
        <!-- == End Jakarta XML Web Services  == -->

    </dependencies>
</project>

5.3.3.2. SOAPサーバのパッケージ構成

SOAPサーバを作成するときの推奨する構成について、説明する。
ガイドラインに従いプロジェクトを追加すると以下の構成となる。

プロジェクト名

説明

[server projectName]-domain
SOAPサーバのドメイン層に関するクラス・設定ファイルを格納するプロジェクト
[server projectName]-web
SOAPサーバのアプリケーション層に関するクラス・設定ファイルを格納するプロジェクト
[server projectName]-env
SOAPサーバの環境に依存するファイル等を格納するプロジェクト
[server projectName]-model
SOAPサーバのドメイン層に関するクラスの中で、Webサービス実行時に使用し、クライアントと共有するクラスを格納するプロジェクト
[server projectName]-webservice
SOAPサーバが提供するWebサービスのインターフェースを格納するプロジェクト

5.3.3.2.1. [server projectName]-domain

[server projectName]-modelの依存関係を追加するため、pom.xmlに以下を追加する。

<dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>artifactId-model</artifactId>
</dependency>

その他のパッケージ構成は、通常のdomainプロジェクトと変わらないため、「アプリケーションのレイヤ化プロジェクト構成」を参照されたい。


5.3.3.2.2. [server projectName]-web

[server projectName]-webserviceの依存関係を追加するため、pom.xmlに以下を追加する。

<dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>artifactId-webservice</artifactId>
</dependency>

Note

依存性の解決について

[server projectName]-modelの依存関係の定義は不要である。これは[server projectName]-webserviceから[server projectName]-modelへの依存関係が定義されているため、推移的に依存関係が追加されるためである。

Note

Jakarta XML Web Servicesでは、XML電文のシリアライズにJAXBを利用しており、アプリケーションの実行にはJAXBがクラスパスに登録されている必要がある。

Apache CXFを利用する場合は、org.glassfish.jaxb:jaxb-runtimeのJAXBが自動的に依存関係に追加されることを確認しているが、本ガイドラインではJAXBの追加で説明のとおり、com.sun.xml.bind:jaxb-implを使用することを前提とするため、terasoluna-gfw-dependenciesで定義しているApache CXFからJAXBの依存関係を抜いている。


[server projectName]-webのプロジェクト推奨構成を、以下に示す。

[server projectName]-web
  └src
      └main
          ├java
          │  └com
          │      └example
          │          ├app...(1)
          │          ├config
          │          │  ├app
          │          │  │  └ApplicationContextConfig.java...(2)
          │          │  ├web
          │          │  │  ├SpringMvcConfig.java...(3)
          │          │  │  └SpringSecurityConfig.java...(4)
          │          │  └ws
          │          │      └[server projectName]WsConfig.java...(5)
          │          └ws...(6)
          │            ├exception...(7)
          │            │  └WsExceptionHandler.java
          │            ├abc
          │            │  └AbcWebServiceImpl.java
          │            └def
          │                └DefWebServiceImpl.java
          ├resources
          │  ├META-INF
          │  │  └spring
          │  │      └application.properties...(8)
          │  └i18n
          │      └application-messages.properties...(9)
          └webapp
              ├resources...(10)
              └WEB-INF
                  ├views ...(11)
                  └web.xml...(12)

項番

説明

(1)
アプリケーション層の構成要素を格納するパッケージ。
Webサービスのみ作成する場合は削除してもよい。
(2)
アプリケーション全体に関するBean定義を行う。
(3)
Spring MVCの設定を行うBean定義を行う。
Webサービスのみ作成する場合は削除してもよい。
(4)
Spring Securityの設定を行うBean定義を行う。
(5)
Webサービスに関するBean定義を行う。
(6)
Webサービスの関連クラスを格納するパッケージ。
(7)
Webサービスの例外ハンドラーなどを格納するパッケージ。
(8)
アプリケーションで使用するプロパティを定義する。
(9)
画面表示用のメッセージ(国際化対応)定義を行う。
(10)
静的リソース(css、js、画像など)を格納する。
Webサービスのみ作成する場合は削除してもよい。
(11)
View(jsp)を格納する。
Webサービスのみ作成する場合は削除してもよい。
(12)
Servletのデプロイメント定義を行う。

Note

SOAPサーバの不要なファイル

SOAPサーバで、Webサービスのみを作成する場合、ブランクプロジェクトに存在するSpring MVCの設定ファイルなどは不要となるため、削除したほうが望ましい。


5.3.3.2.3. [server projectName]-env

[server projectName]-envについては、通常のenvプロジェクトと変わらないため、「アプリケーションのレイヤ化プロジェクト構成」を参照されたい。


5.3.3.2.4. [server projectName]-model

[server projectName]-modelのプロジェクト推奨構成を、以下に示す。

[server projectName]-model
  └src
      └main
          └java
              └com
                  └example
                      └domain ...(1)
                          └model ...(2)
                              ├Xxx.java
                              ├Yyy.java
                              └Zzz.java

項番

説明

(1)
ドメイン層の構成要素を格納するパッケージ。
(2)
Domain Objectの中でWebサービス実行時に使用するクラスを格納するパッケージ。

5.3.3.2.5. [server projectName]-webservice

[server projectName]-webserviceのプロジェクト推奨構成を、以下に示す。

[server projectName]-webservice
  └src
      └main
          └java
              └com
                  └example
                      └ws...(1)
                        ├webfault...(2)
                        ├abc
                        │  └AbcWebService.java
                        └def
                            └DefWebService.java

項番

説明

(1)
Webサービスのインターフェースを格納するパッケージ。
(2)
Webサービスのwebfaultを格納するパッケージ。

5.3.3.3. クライアントのパッケージ構成

クライアントを作成するときの推奨する構成について、説明する。
ガイドラインに従いプロジェクトをSOAPサーバから提供されると以下の構成となる。

プロジェクト名

説明

[client projectName]-domain
クライアントのドメイン層に関するクラス・設定ファイルを格納するプロジェクト
[client projectName]-web
クライアントのアプリケーション層に関するクラス・設定ファイルを格納するプロジェクト
[client projectName]-env
クライアントの環境に依存するファイル等を格納するプロジェクト

Note

[server projectName]-modelと[server projectName]-webserviceについては、前述の「 SOAPサーバのパッケージ構成」を参照されたい。


5.3.3.3.1. [client projectName]-domain

SOAPサーバから提供される[server projectName]-webserviceの依存関係を追加するため、pom.xmlに以下を追加する。

<dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>artifactId-webservice</artifactId>
</dependency>

Note

依存性の解決について

[server projectName]-webと同様に、このpom.xmlには、[server projectName]-modelの依存関係の定義は不要である。これは[server projectName]-webserviceから[server projectName]-modelへの依存関係が定義されているため、推移的に依存関係が追加されるためである。

その他のパッケージ構成は、通常のdomainプロジェクトと変わらないため、「アプリケーションのレイヤ化プロジェクト構成」を参照されたい。


5.3.3.3.2. [client projectName]-web

[client projectName]-webについては、通常のwebプロジェクトと変わらないため、「アプリケーションのレイヤ化プロジェクト構成」を参照されたい。

5.3.3.3.3. [client projectName]-env

[client projectName]-envのプロジェクト推奨構成を、以下に示す。

[projectName]-env
  ├configs ...(1)
  │   └[envName] ...(2)
  │       ├java ...(3)
  │       └resources ...(4)
  └src
      └main
          ├java ...(5)
          │  └com
          │      └example
          │          └config
          │             └app
          │                └[projectName]EnvConfig.java...(6)
          └resources ...(7)
              ├META-INF
              │  └spring
              │      └[projectName]-infra.properties ...(8)
              └logback.xml ...(9)

項番

説明

(1)
全環境の環境依存ファイルを管理するためのディレクトリ。
(2)
環境毎の環境依存ファイルを管理するためのディレクトリ。
ディレクトリ名は、環境を識別する名前を指定する。
(3)
環境毎のJavaで記載したBean定義を管理するためのディレクトリ。
サブディレクトリの構成や管理する設定ファイルは、(5)と同様。
(4)
環境毎の設定ファイルを管理するためのディレクトリ。
サブディレクトリの構成や管理する設定ファイルは、(7)と同様。
(5)
ローカル開発環境用のJavaで記載したBean定義を管理するためのディレクトリ。
(6)
ローカル開発環境用のBean定義を行う。
(7)
ローカル開発環境用の設定ファイルを管理するためのディレクトリ。
(8)
ローカル開発環境用のプロパティを定義する。
WSDLのURLなど環境ごとに変更の可能性がある値を設定する。
(9)
ローカル開発環境用のログ出力定義を行う。

5.3.3.4. WebServiceインターフェースの実装クラスとWSDLファイルの関係

Webサービスを作成する際、登場するネームスペース、サービス、ポートの説明と、それぞれの関係を説明する。
ネームスペース、サービス、ポートは、WSDLに定義される項目となるため、WSDLファイルの構造とともに説明を行う。

公開されるWSDLファイル

<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                xmlns:tns="http://example.com/todo"
                xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
                xmlns:ns1="http://schemas.xmlsoap.org/soap/http"
                targetNamespace="http://example.com/todo"> <!-- (1) -->

    // omitted

    <wsdl:binding name="TodoWebServiceSoapBinding" type="tns:TodoWebService">
        <soap12:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="getTodo">
            <soap12:operation soapAction="" style="document" />
            <wsdl:input name="getTodo">
                <soap12:body use="literal" />
            </wsdl:input>
            <wsdl:output name="getTodoResponse">
                <soap12:body use="literal" />
            </wsdl:output>
            <wsdl:fault name="WebFaultException">
                <soap12:fault name="WebFaultException" use="literal" />
            </wsdl:fault>
        </wsdl:operation>
    </wsdl:binding>

    <wsdl:service name="TodoWebService">  <!-- (2) -->
        <wsdl:port binding="tns:TodoWebServiceSoapBinding" name="TodoWebPort">  <!-- (3) -->
            <soap12:address location="http://AAA.BBB.CCC.DDD:XXXX/[server projectName]-web/ws/TodoWebService" />  <!-- (4) -->
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

項番

説明

(1)
WebServiceで定義するtargetNamespaceは、名前空間を指定する項目となり、サービスやポート等の要素を一意に特定するために使用される。
WSDLでは、最上位要素である<wsdl:definitions>に設定され、この名前空間をもとに他の要素(service, port)を特定する。
(2)
WebServiceで定義するserviceNameは、WSDLの<wsdl:service>要素に対応する。
この要素は、一つ以上の<wsdl:port>要素を子要素として持ち、クライアントが利用できるエンドポイント(port)の情報を集約する役割を担う。
(3)
WebServiceで定義するportNameは、WSDLの<wsdl:port>要素に対応する。
ポートは、関連付けられたバインディング(<wsdl:binding>)使用して、Webサービスがどのように振る舞うかを規定する。
クライアントはこのポート情報をもとにWebサービスにアクセスを行う。

<wsdl:binding>は、プロトコルや、データ形式、入出力、エラー等の情報を定義する要素となる。
詳しくは、「Web Services Description Language (WSDL) 1.1」を参照されたい。
(4)
addressは、WebServiceで直接定義する項目ではなく、Jakarta XML Web Services実装ライブラリによって自動的に設定される。
location属性に実際のエンドポイントアドレスが定義され、このアドレスがSOAPサーバーにアクセスする際に利用される。
Jakarta XML Web Services実装ライブラリが、<soap:address>要素に定義されたエンドポイントアドレスを特定するため、利用者は特に意識する必要はない。

5.3.3.5. テスト等でエンドポイントアドレスを上書き指定したい場合

WSDLファイルには、Webサービス実行時のアクセスURL(エンドポイントアドレス)が記述されているため、クライアントではアクセスURLの設定は不要である。
ただし、WSDLファイルに記述されているURLではないURLにアクセスする場合、jakarta.xml.ws.BindingProviderで取得できるRequestContextBindingProvider.ENDPOINT_ADDRESS_PROPERTYを設定することで上書きすることができる。
テストなどで、環境を切り替える場合に使用するとよい。
以下はその設定例である。

jakarta.xml.ws.Serviceの定義が複雑になるため、FactoryBeanを使用してBean定義を行う。

[client projectName]-domain/src/main/java/com/example/domain/service/todo/factory/TodoWebServiceFactoryBean.java

public class TodoWebServiceFactoryBean implements
                                            FactoryBean<TodoWebService> {  // (1)

    private String wsdlDocumentResource;

    private String endpointAddress;

    @Override
    public TodoWebService getObject() throws Exception {
        QName serviceName = new QName("http://example.com/todo", "TodoWebService");
        QName portName = new QName("http://example.com/todo", "TodoWebPort");

        Service service = Service.create(new URL(wsdlDocumentResource), serviceName);
        TodoWebService todoWebService = service.getPort(portName, TodoWebService.class);

        BindingProvider bindingProvider = (BindingProvider) todoWebService;  // (2)
        Map<String, Object> requestContext = bindingProvider.getRequestContext();  // (3)
        requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAddress);  // (4)

        return todoWebService;
    }

    public void setWsdlDocumentResource(String wsdlDocumentResource) {
        this.wsdlDocumentResource = wsdlDocumentResource;
    }

    // (4)
    public void setEndpointAddress(String endpointAddress) {
        this.endpointAddress = endpointAddress;
    }

    @Override
    public Class<?> getObjectType() {
        return TodoWebService.class;
    }
}

項番

説明

(1)
FactoryBeanを実装する。
(2)
生成した動的プロキシをBindingProviderに変換する。
(3)
BindingProviderからRequestContextを取得する。
(4)
取得したRequestContextに対して、エンドポイントアドレスの設定を追加する。
キーにBindingProvider.ENDPOINT_ADDRESS_PROPERTYを設定し、値にはエンドポイントアドレスを設定する。
エンドポイントアドレスの値は外部から指定できるように、FactoryBeansetterとして定義を行う。

Note

BindingProviderには、他にも設定できる項目や、使用用途が存在している。

BindingProviderの詳細については Jakarta XML Web Services 4.0 -4.2 jakarta.xml.ws.BindingProvider-を参照されたい。


FactoryBeanのBean定義を行う。

  • [client projectName]-domain/src/main/java/com/example/config/app/[client projectName]DomainConfig.java

    @Value("${webservice.todoWebService.wsdlDocumentResource}")
    private String wsdlDocumentResource;
    
    @Value("${webservice.todoWebService.endpointAddress}")  // (1)
    private String endpointAddress;
    
    @Bean("todoWebService")
    public TodoWebServiceFactoryBean todoWebService() {
        TodoWebServiceFactoryBean factory = new TodoWebServiceFactoryBean();
        factory.setWsdlDocumentResource(wsdlDocumentResource);
        factory.setEndpointAddress(endpointAddress);  // (1)
        return factory;
    }
    
  • [client projectName]-env/src/main/resources/META-INF/spring/[client projectName]-infra.properties

    # (2)
    webservice.todoWebService.endpointAddress=http://AAA.BBB.CCC.DDD:XXXX/[server projectName]-web/ws/TodoWebService
    

    項番

    説明

    (1)
    後述するプロパティファイルからエンドポイントアドレスの値を取得し、FactoryBeanに設定する。
    (2)
    エンドポイントアドレスを定義する。