12.6. SOAP Web Service(サーバ/クライアント)¶
Caution
Spring Framework 6へのバージョンアップに伴い、Spring#27422でorg.springframework.remoting
パッケージが削除された。これに伴い、本章で説明しているSpring Frameworkが提供していたJAX-WSの連携機能を使用した処理が使えなくなった。
Springの機能を使用してSOAP Web Serviceの開発を行いたい場合は、Spring Web Servicesが提供されているため、Spring Web Servicesを使用することを検討されたい。
目次
12.6.1. Overview¶
本節では、SOAP Web Serviceの基本的な概念とJAX-WSを使用したSOAPサーバ、クライアント双方の開発について説明する。
実装に対する具体的な説明については、
- JAX-WSを使用したSOAP Web Serviceのアプリケーション構成やAPIの実装方法について説明している。
を参照されたい。
12.6.1.1. SOAPとは¶
項番 | 説明 |
---|---|
(1)
|
クライアントは、別のSOAPサーバへの通信を行うWebアプリケーションを想定している。
クライアントと呼んでいるがWebアプリケーション想定なので注意が必要である。
|
(2)
|
SOAPサーバは、Webサービスを公開し、クライアントからのSOAP Web ServiceによるXMLを受信して処理を行う。データベースなどにアクセスを行い、業務処理を行うことを想定している。
|
(3)
|
SOAP Web ServiceではXMLを使用して情報のやり取りを行う。
今回の想定では、SOAPサーバ、クライアントどちらもJavaである想定としているが、他のプラットフォームでも問題なく通信可能である。
|
12.6.1.2. JAX-WSとは¶
12.6.1.3. JAX-WSを利用したWebサービスの開発について¶
Note
APサーバのJAX-WS実装によって、JAX-WS仕様への対応状況や実際のWebサービスの動作やが異なる場合があり、必ずしも本ガイドラインの実装が全てのAPサーバで同様に動作するわけではない。
開発を始める前には必ず、「アプリケーションの設定」のNoteから使用するAPサーバのマニュアルを確認されたい。
12.6.1.4. Spring FrameworkのJAX-WS連携機能について¶
項番 | 説明 |
---|---|
(1)
|
[クライアント] ControllerがServiceを呼び出す。
通常の呼び出しと変更点は特にない。
|
(2)
|
[クライアント] ServiceがSOAPサーバ提供側で用意したWebServiceインターフェースを呼び出す。
この図では、ServiceがWebServiceインターフェースを呼び出しているが、要件に応じてControllerから直接WebServiceインターフェースを呼び出してもよい。
|
(3)
|
[クライアント] WebServiceインターフェースが呼び出されると実体として「動的プロキシ(Dynamic Proxy)」(以下「プロキシ」)が呼び出される。
このプロキシは
org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean が生成した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 WebService実装クラスは、Spring Frameworkが提供するDispatcherServlet上ではなく、APサーバのJAX-WSエンジンが実装するサーブレットとして動作する。このためガイドラインのアプリケーション層の実装に記載している実装方法とは以下のような違いがあることに注意されたい。
また、SOAPサーバは、
上記に対して、 |
(6)
|
[サーバ] WebService実装クラスでは、業務処理を行うServiceを呼び出す。
|
(7)
|
[サーバ] Serviceでは、Repositoryなどを使用して業務処理を実行する。
通常の呼び出しと変更点は特にない。
|
Note
SpringでのJAX-WS実装の詳細は、Spring Framework Documentation -Remoting and Web Services-を参照されたい。
12.6.1.4.1. JAX-WSを利用したWebサービスのモジュールの構成¶
JAX-WSを利用したWebサービスを作成する場合、既存のブランクプロジェクトとは別に以下2つのプロジェクトを追加することを推奨する。
- modelプロジェクト
- webserviceプロジェクト
本ガイドラインでは、マルチプロジェクトで以下のような構成を用いる。
ここでもクライアントはWebアプリケーションであることを前提とするが、デスクトップアプリケーションやコマンドラインインターフェースから呼び出す場合も基本的な考え方は同じである。
項番 | 説明 |
---|---|
(1)
|
クライアントを作成する場合、従来のマルチプロジェクトにSOAPサーバから提供されるmodelプロジェクトとwebserviceプロジェクトを追加する。
ここではサーバとクライアントをともに開発することを前提としている。
これらのプロジェクトの詳細については「SOAPサーバの作成」で説明する。
追加方法については「SOAPサーバ用にプロジェクトの設定を変更する」を参照されたい。
サーバとクライアントの開発が別々で、modelプロジェクトとwebserviceプロジェクトが提供されない場合、もしくはJava以外でSOAPサーバが作成されている場合には、modelプロジェクト内のDomain Objectとwebserviceプロジェクト内のWebサービスインターフェースを自分で作成する必要がある。
wsimportを使用することで、WSDLから簡単にDomain ObjectとWebサービスインターフェースを作成することができる。
詳細については「wsimportについて」を参照されたい。
|
(2)
|
SOAPサーバを作成する場合、従来のマルチプロジェクトに追加してmodelプロジェクトとwebserviceプロジェクトを追加する。
クライアントにこれら2つのプロジェクトを公開する。
クライアントへのmodelプロジェクト、webserviceプロジェクトの公開方法は、Mavenの依存関係への追加を想定している。
|
以下は、クライアントのプロジェクト構成である。
12.6.1.5. Webサービスとして公開されるURL¶
- http://AAA.BBB.CCC.DDD:XXXX/コンテキストルート/Webサービス名?wsdl
WSDL内で定義されるエンドポイントアドレスは以下のURLである。
- http://AAA.BBB.CCC.DDD:XXXX/コンテキストルート/Webサービス名
Note
本ガイドラインでは、マルチプロジェクト構成のwebプロジェクトをWARファイル化して、APサーバにデプロイする前提である。その場合、コンテキストルートは基本的に、[server projectName]-webとなる。ただし、APサーバによって異なるので注意すること。
Note
本ガイドラインでは、SOAPサーバ、クライアントともにWebアプリケーションとして公開する前提であるため、クライアントではWSDLのURLを指定している。URLではなく、WSDLをファイルとして用意してクライアントを作成することも可能である。 詳細は、Webサービス クライアントの実装を参照されたい。
Warning
本ガイドラインでは、APサーバ(Tomcatの場合は使用するライブラリ)でコンテキストルートのマッピングを切り替え以下のようなURLでアクセスするように設定している。
- http://AAA.BBB.CCC.DDD:XXXX/[server projectName]-web/ws/TodoWebService?wsdl
このコンテキストルート直下ではないURLにWebサービスをマッピングさせる方法は、APサーバごとに異なる。 詳細は以下を参照してほしい。
項番 APサーバ名 説明 (1) Apache Tomcat (2) Oracle WebLogic Server TBD (3) JBoss Enterprise Application Platform TBD
12.6.2. How to use¶
本節では、SOAP Web Serviceの具体的な作成方法について説明する。
12.6.2.1. SOAPサーバの作成¶
12.6.2.1.1. プロジェクトの構成¶
各プロジェクトの依存関係
項番 | プロジェクト名 | 説明 |
---|---|---|
(1)
|
webプロジェクト
|
Webサービス実装クラスを配置する。
|
(2)
|
domainプロジェクト
|
WebServiceの実装クラスから呼び出されるServiceを配置する。
その他、Repositoryなどは従来と同じである。
|
(3)
|
webserviceプロジェクト
|
公開するWebServiceのインターフェースをここに配置する。
クライアントはこのインターフェースを使用してWebサービスを実行する。
|
(4)
|
modelプロジェクト
|
ドメイン層に属するクラスのうち、SOAP Web Serviceで使用するクラスのみをここに配置する。
クライアントからの入力値や返却結果はこのプロジェクト内のクラスを使用する。
|
12.6.2.1.2. アプリケーションの設定¶
Webサービスを公開する際の初期設定
Note
以下、参考資料として、APサーバのマニュアルを記述しておく。 必ず、使用するバージョンとあっているか確認してから参照すること。
Oracle WebLogic Server 12.2.1.4: Understanding WebLogic Web Services for Oracle WebLogic Server -Features and Standards Supported by WebLogic Web Services-
Oracle WebLogic Server 14.1.1.0: Understanding WebLogic Web Services for Oracle WebLogic Server -Features and Standards Supported by WebLogic Web Services-
JBoss Enterprise Application Platform 7.3: DEVELOPING JAX-WS WEB SERVICES
JBoss Enterprise Application Platform 6.4: JAX-WS WEB SERVICES
WebSphere Application Server 9.0: IBM Knowledge Center - Web services
パッケージのコンポーネントスキャン設定
Webサービスで使用するコンポーネントをスキャンするため、[server projectName]-ws.xml
を作成し、コンポーネントスキャンの定義を行い、Webサービスにインジェクションできるようにする。
[server projectName]-web/src/main/resources/META-INF/spring/[server projectName]-ws.xml
<?xml version="1.0" encoding="UTF-8"?>
<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"
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">
<!-- (1) -->
<context:component-scan base-package="com.example.ws" />
</beans>
項番 | 説明 |
---|---|
(1)
|
Webサービスで使用するコンポーネントが格納されているパッケージを指定する。
|
[server projectName]-web/src/main/webapp/WEB-INF/web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- Root ApplicationContext -->
<!-- (1) -->
<param-value>
classpath*:META-INF/spring/applicationContext.xml
classpath*:META-INF/spring/spring-security.xml
classpath*:META-INF/spring/[server projectName]-ws.xml
</param-value>
</context-param>
項番 | 説明 |
---|---|
(1)
|
[server projectName]-ws.xml をルートApplicationContext 生成時の読み込み対象に加える。 |
入力チェックを行うための定義
[server projectName]-web/src/main/resources/META-INF/spring/applicationContext.xml
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
<property name="validator" ref="validator" />
</bean>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
12.6.2.1.3. Webサービスの実装¶
以下の作成を行う。
- Domain Objectの作成
- WebServiceインターフェイスの作成
- WebService実装クラスの作成
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 javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.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サービスのパッケージ名と合わせることを推奨する。Warning
Note
|
(2)
|
Webサービスのメソッドとして公開するメソッドに
@WebMethod を付ける。このアノテーションを付けることにより、WSDL上にメソッドが公開され、外部から使用することが可能になる。
|
(3)
|
返り値に
@WebResult を付け、名前をname 属性に指定する。返り値がない場合は不要。このアノテーションを付けることにより、WSDL上に返り値として公開される。
|
(4)
|
引数に
@WebParam を付け、名前をname 属性に指定する。このアノテーションを付けることにより、WSDL上に引数が公開され、外部から呼び出すときの必要なパラメータとして定義される。
WebFaultException の詳細は「例外ハンドリングの実装」を参照されたい。 |
Note
パッケージ名および、ネームスペースの付け方について
パッケージ名が以下のような形式になっている場合
- 【ドメイン】.【アプリケーション名(システム名)】.ws.【ユースケース名】
本ガイドラインでは、以下のようなネームスペースにすることを推奨する。
- http://【ドメイン】/【アプリケーション名(システム名)】
Note
ネームスペースとパッケージ名の関係
ドメインをcom.example、アプリケーション名をtodoとした場合、Namespaceは以下のようなJavaのパッケージと紐づけられる。
仕様ではないが、Namespaceとパッケージの命名について、XML Namespace Mapping(Red Hat JBoss Fuse)にまとまっている。
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 javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.xml.ws.BindingType;
import javax.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(
portName = "TodoWebPort",
serviceName = "TodoWebService",
targetNamespace = "http://example.com/todo",
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の実装クラスであることを宣言する。portName 属性は、WSDL上のポート名として公開される。serviceName 属性は、WSDL上のサービス名として公開される。targetNamespace 属性は、WSDL上で使用されるネームスペース。endpointInterface 属性は、このクラスが実装しているWebサービスのインターフェース名を定義する。Note
|
(2)
|
@BindingType を付けることで、バインディングの方式を設定する。SOAPBinding.SOAP12HTTP_BINDING を定義するとSOAP1.2でのバインディングとなる。何もつけない場合は、SOAP1.1でのバインディングとなる。
Note 使用するAPサーバのJAX-WS実装により、バインディング方式で挙動が異なる場合があるため注意すること。 たとえば、WebSphere Application Serverの特定のバージョンではSOAP1.2でのバインディングの場合にWSDLが自動生成されない。詳細についてはIBM Knowledge Center - Using annotations to create web servicesを参照されたい。 |
(3)
|
先ほど作成した
TodoWebService インターフェースを実装する。org.springframework.web.context.support.SpringBeanAutowiringSupport を継承することで、SpringのBeanをDIできるようにする。 |
(4)
|
Serviceをインジェクションする。
通常のControllerでServiceを呼び出す場合と変わりはない。
|
(5)
|
Serviceを呼び出して業務処理を実行する。
通常のControllerでServiceを呼び出す場合と変わりはない。
|
Note
Webサービス関連のクラスはwsパッケージ配下にまとめることを推奨する。これは、アプリケーション層のクラスはappパッケージ配下に配置することを推奨しており、それらと区別をしやすくするためである。
12.6.2.1.4. 入力チェックの実装¶
[server projectName]-domain/src/main/java/com/example/domain/service/todo/TodoService.java
package com.example.domain.service.todo;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.groups.Default;
import org.springframework.validation.annotation.Validated;
import com.example.domain.model.Todo;
@Validated // (1)
public interface TodoService {
Todo getTodo(@NotNull String todoId); // (2)
Todo createTodo(@Valid Todo todo); // (3)
@Validated({ Default.class, Todo.Update.class }) // (4)
Todo updateTodo(@Valid Todo todo);
}
項番 | 説明 |
---|---|
(1)
|
@Validated を付けることで、このインターフェースの実装クラスが入力チェック対象であることを宣言する。 |
(2)
|
引数をチェックする場合には、引数自体にアノテーションを付ける。
|
(3)
|
JavaBeanの入力チェックを行う場合も、引数に
@Valid を付ける。 |
(4)
|
@Validated にグループを指定し、特定の条件を絞って入力チェックすることも可能である。グループの詳細は次のJavaBeanの説明で記述する。
|
[server projectName]-model/src/main/java/com/example/domain/model/Todo.java
package com.example.domain.model;
import javax.validation.constraints.NotNull;
import javax.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)
@NotNull(groups = Update.class)
private String todoId;
@NotNull
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)
|
バリデーションのグループ化を行うために使用するインターフェースを定義する。
|
12.6.2.1.5. セキュリティ対策¶
認証処理
以下にSOAP Web Serviceに対して、Basic認証を行うSpring Securityの設定例を示す。
[server projectName]-web/src/main/resources/META-INF/spring/spring-security.xml
<!-- (1) -->
<sec:http pattern="/ws/**" request-matcher="ant" create-session="stateless" >
<sec:csrf disabled="true" />
<sec:http-basic />
</sec:http>
<!-- (2) -->
<sec:authentication-manager>
<sec:authentication-provider user-service-ref="sampleUserDetailsService" />
</sec:authentication-manager>
項番 | 説明 |
---|---|
(1)
|
sec:http-basic タグを記述するとBasic認証を行うことができる。pattern 属性を使用して、Webサービスを実行する部分のみ認証を行う。 |
(2)
|
authentication-provider を利用して、認証方式を定義する。実際の認証およびユーザ情報取得は
UserDetailsService を作成して実施する必要がある。詳細は「認証」を参照されたい。
|
認可処理
[server projectName]-web/src/main/resources/META-INF/spring/spring-security.xml
<sec:global-method-security pre-post-annotations="enabled" /> <!-- (1) -->
項番 | 説明 |
---|---|
(1)
|
<sec:global-method-security> 要素のpre-post-annotations 属性をenabled に指定する。 |
[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対策
[server projectName]-web/src/main/resources/META-INF/spring/spring-security.xml
<!-- (1) -->
<sec:http pattern="/ws/**" request-matcher="ant" create-session="stateless">
<sec:http-basic />
<sec:csrf disabled="true" />
</sec:http>
項番 | 説明 |
---|---|
(1)
|
SOAP Web Service用のSpring Securityの定義を追加する。
<sec:http> 要素のpattern 属性にSOAP Web Service用のリクエストパスのURLパターンを指定する。このコード例では、
/ws/ で始まるリクエストパスをSOAP Web Service用のリクエストパスとしている。また、
create-session 属性をstateless とする事で、Spring Securityの処理でセッションが使用されなくなる。CSRF対策を無効化するために、
<sec:csrf> 要素のdisabled 属性をtrue に指定する。 |
12.6.2.1.6. 例外ハンドリングの実装¶
SOAPサーバで発生する例外
SOAPサーバで発生した例外はこれから記述する例外を実装したクラス(SOAPFault)を使用することで、クライアントへの通知メッセージを決定することができる。
具体的には以下のクラスを作成する。
項番 | クラス名 | 概要 |
---|---|---|
(1)
|
ErrorBean |
発生した例外のコードとメッセージなどを保持するクラス。
|
(2)
|
WebFaultType |
例外の種類を判別するために使用する列挙型。
|
(3)
|
WebFaultBean |
ErrorBean とWebFaultType を保持するクラス。ErrorBean をList で保持して例外情報を複数保持できる。 |
(4)
|
WebFaultException |
WebFaultBean を保持する例外クラス。 |
これらの例外は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)
|
ErrorBean とWebFaultType を保持するクラスを作成する。 |
[server projectName]-webservice/src/main/java/com/example/ws/webfault/WebFaultException.java
package com.example.ws.webfault;
import java.util.List;
import javax.xml.ws.WebFault;
@WebFault(name = "WebFault", targetNamespace = "http://example.com/todo") // (1)
public class WebFaultException extends Exception {
private WebFaultBean faultInfo; // (2)
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)
|
faultInfoをフィールドに保持させるとともに、コード例のように以下のようなコンストラクタとメソッドを持たせる。
|
Note
WebFaultExceptionにRuntimeExceptionではなく、Exceptionを継承させている理由
WebFaultException
の親クラスをRuntimeException
にすれば、例外の処理をもっと簡略化することができそうに見える。しかし、親クラスをRuntimeException
にしてはいけない。JSR 224: JavaTM API for XML-Based Web Servicesでも明確にしてはいけないと宣言されている。実際に試してみても、APサーバのJAX-WS実装次第ではあるが、クライアントで@WebFault
を付けた例外クラス(WebFaultException
)を取得することができず、エラーの種類やメッセージを取得することができなくなる。AOPを使用して例外処理を実施していないのもException
を継承しているためである。
Warning
WebFaultExceptionのコンストラクタとフィールドについて
WebFaultException
には、デフォルトコンストラクタと各フィールドに対応するsetterが必須となる。これは、クライアントの内部処理で、WebFaultException
を作成する際に使用するためである。そのため、各フィールドをfinalにすることも不可能である。
WebFaultException
を継承し、クライアントへ伝えたい種類分、子クラスを作成する。- 業務エラー例外
- 入力エラー例外
- リソース未検出エラー例外
- 排他エラー例外
- 認可エラー例外
- システムエラー例外
下記は、業務エラー例外の例である。
[server projectName]-webservice/src/main/java/com/example/ws/webfault/BusinessFaultException.java
package com.example.ws.webfault;
import javax.xml.ws.WebFault;
@WebFault(name = "BusinessFault", targetNamespace = "http://example.com/todo") // (1)
public class BusinessFaultException extends WebFaultException {
public BusinessFaultException(String message, WebFaultBean faultInfo) {
super(message, faultInfo);
}
public BusinessFaultException(String message, WebFaultBean faultInfo, Throwable e) {
super(message, faultInfo, e);
}
}
項番 | 説明 |
---|---|
(1)
|
WebFaultException を継承し、コンストラクタのみ作成する。フィールドやその他メソッドは親クラスのメソッドを使用するため記述不要である。
|
発生する例外をSOAPFaultでラップする例外ハンドラー
Serviceから発生する実行時例外をSOAPFaultでラップするために例外ハンドラークラスを作成する。 本ガイドラインではWebService実装クラスがこのハンドラーを用いて例外を変換してスローする方針とする。
Serviceからスローされる例外は以下を想定している。必要に応じて追加されたい。
例外名 | 内容 |
---|---|
org.springframework.security.access.AccessDeniedException |
認可エラー時の例外
|
javax.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 javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.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.ex.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部分)では、クライアントでは詳細な例外の内容は通知されず、com.sun.xml.internal.ws.fault.ServerSOAPFaultException
が発生するのみとなる。他の例外同様にラップしてクライアント側に通知することも可能である。
Serviceで発生した例外をWebサービス内から例外ハンドラーを呼び出し、ラップする
Webサービスクラスにて、例外ハンドラーを呼び出す。以下はその例である。
[server projectName]-web/src/main/java/com/example/ws/todo/TodoWebServiceImpl.java
@WebService(
portName = "TodoWebPort",
serviceName = "TodoWebService",
targetNamespace = "http://example.com/todo",
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)
|
実行時例外が発生した場合は、例外ハンドラークラスに処理を委譲する。
|
12.6.2.1.7. MTOMを利用した大容量のバイナリデータを扱う方法¶
[server projectName]-webservice/src/main/java/com/example/ws/todo/TodoWebService.java
package com.example.ws.todo;
import java.util.List;
import javax.activation.DataHandler;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.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)
|
バイナリデータを処理する
javax.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 javax.activation.DataHandler;
import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.xml.ws.BindingType;
import javax.xml.ws.soap.MTOM;
import javax.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(
portName = "TodoWebPort",
serviceName = "TodoWebService",
targetNamespace = "http://example.com/todo",
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)
|
javax.activation.DataHandler からjava.io.InputStream を取得してファイルを扱う。 |
12.6.2.2. クライアントの作成¶
12.6.2.2.1. プロジェクトの構成¶
「JAX-WSを利用したWebサービスの開発について」で述べたとおり、modelプロジェクトとwebserviceプロジェクトを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を定義する。
12.6.2.2.2. Webサービス クライアントの実装¶
以下のクラスの実装を行う。
- WebServiceインターフェースを実装したプロキシの定義
- ServiceクラスからWebServiceインターフェース経由でWebサービスを呼び出す。
WebServiceインターフェースを実装したプロキシの作成
WebServiceインターフェースを実装したプロキシを生成するorg.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean
の定義を行う。
[client projectName]-domain/src/main/resources/META-INF/spring/[client projectName]-domain.xml
<bean id="todoWebService"
class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean"><!-- (1) -->
<property name="serviceInterface" value="com.example.ws.todo.TodoWebService" /><!-- (2) -->
<!-- (3) -->
<property name="serviceName" value="TodoWebService" />
<property name="portName" value="TodoWebPort" />
<property name="namespaceUri" value="http://example.com/todo" />
<property name="wsdlDocumentResource" value="${webservice.todoWebService.wsdlDocumentResource}" /><!-- (4) -->
<property name="lookupServiceOnStartup" value="false" /><!-- (5) -->
</bean>
[client projectName]-env/src/main/resources/META-INF/spring/[client projectName]-infra.properties
# (6)
webservice.todoWebService.wsdlDocumentResource=http://AAA.BBB.CCC.DDD:XXXX/[server projectName]-web/ws/TodoWebService?wsdl
項番 | 説明 |
---|---|
(1)
|
org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean を定義する。このクラスが生成するプロキシを経由してSOAPサーバにアクセスできる。 |
(2)
|
serviceInterface プロパティに本来このWebサービスが実装すべきインターフェースを定義する。 |
(3)
|
serviceName 、portName 、namespaceUri プロパティにそれぞれSOAPサーバ側で定義している同じ内容を定義する必要がある。 |
(4)
|
wsdlDocumentResource プロパティに公開されているWDSLのURLを設定する。ここでは後述するプロパティファイルにURLを記述するため、プロパティのキーを指定している。
|
(5)
|
lookupServiceOnStartup プロパティにBean生成する時、SOAPサーバからWSDLファイルを取得するかどうかを設定する。falseの場合はBeanが初めて使用されるタイミングでWSDLファイルの取得が行われる。SOAPサーバからWSDLファイルの取得が不可能な場合でもWebサービス クライアントを起動させるために、
lookupServiceOnStartup プロパティにfalse を指定することを推奨する。ただし、WSDLファイルをWebサービス クライアントで保持している場合は設定不要である。 |
(6)
|
[client projectName]-domain.xml で定義したプロパティのキーの値を設定する。WSDLのURLを記述する。Note wsdlDocumentResourceへのWSDLファイルのURL以外の指定 上記の例では、SOAPサーバがWSDLファイルを公開している前提である。 |
Note
エンドポイントアドレスの上書き指定
WSDLファイルには、Webサービス実行時のアクセスURL(エンドポイントアドレス)が記述されているため、クライアントではアクセスURLの設定は不要である。
ただし、WSDLファイルに記述されているURLではないURLにアクセスする場合、org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean
のendpointAddress
プロパティを設定することで上書きすることができる。
テストなどで、環境を切り替える場合に使用するとよい。
以下はその設定例である。
[client projectName]-domain/src/main/resources/META-INF/spring/[client projectName]-domain.xml
<bean id="todoWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean"> <property name="serviceInterface" value="com.example.ws.todo.TodoWebService" /> <property name="serviceName" value="TodoWebService" /> <property name="portName" value="TodoWebPort" /> <property name="namespaceUri" value="http://example.com/todo" /> <property name="wsdlDocumentResource" value="${webservice.todoWebService.wsdlDocumentResource}" /> <property name="endpointAddress" value="${webservice.todoWebService.endpointAddress}" /><!-- (1) --> <property name="lookupServiceOnStartup" value="false" /> </bean>
[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) エンドポイントアドレスを設定する。ここでは後述するプロパティファイルにURLを記述するため、プロパティのキーを指定している。 (2)[client projectName]-domain.xml
で定義したプロパティのキーの値を設定する。エンドポイントアドレスを記述する。
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)
|
Note
レスポンスの情報取得
リトライを考慮するなど、レスポンスの情報をクライアントで取得したい場合、以下のようにjavax.xml.ws.BindingProvider
クラスにキャストすることで取得できる。
BindingProvider provider = (BindingProvider) todoWebService; int status = (int) provider.getResponseContext().get(MessageContext.HTTP_RESPONSE_CODE);
BindingProvider
の詳細については The Java API for XML-Based Web Services(JAX-WS) 2.2 -4.2 javax.xml.ws.BindingProvider-を参照されたい。
ただし、クライアントの依存関係にApatch CXFライブラリが含まれる場合、通信エラー時に上記の方法でレスポンスの情報を取得することができない。 これは、依存関係にApatch CXFライブラリが含まれる場合は自動的にApatch CXFのプロキシが使用されるため、およびApache CXFのプロキシは通信エラーが発生した場合にレスポンスの情報をレスポンスコンテキストに保持しないためである。 Apache CXFのエラー処理についてはApache CXF Software Architecture Guide -Fault Handling-を参照されたい。
Webサービスと別のWebサービスへのクライアントを持つ中継サービスのように、どうしてもクライアントにApache CXFライブラリの依存関係を含んでしまう場合は制限事項として注意されたい。
12.6.2.2.3. セキュリティ対策¶
認証処理
org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean
を使用している場合でBasic認証を使用しているSOAPサーバと通信をする場合には、bean定義にユーザ名とパスワードを追加するだけで認証を行うことができる。
[client projectName]-domain/src/main/resources/META-INF/spring/[client projectName]-domain.xml
<bean id="todoWebService"
class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
<property name="serviceInterface" value="com.example.ws.todo.TodoWebService" />
<property name="serviceName" value="TodoWebService" />
<property name="portName" value="TodoWebPort" />
<property name="namespaceUri" value="http://example.com/todo" />
<property name="wsdlDocumentResource" value="${webservice.todoWebService.wsdlDocumentResource}" />
<!-- (1) -->
<property name="username" value="${webservice.todoWebService.username}" />
<property name="password" value="${webservice.todoWebService.password}" />
</bean>
[client projectName]-env/src/main/resources/META-INF/spring/[client projectName]-infra.properties
# (2)
webservice.todoWebService.username=testuser
webservice.todoWebService.password=password
項番 | 説明 |
---|---|
(1)
|
org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean のbean定義にusernameとpasswordを加えることでBasic認証における、認証情報を送信することができる。ユーザ名とパスワードをプロパティファイルに切り出した場合のサンプルである。
|
(2)
|
[client projectName]-domain.xml で定義したプロパティのキーの値を設定する。認証に使用するユーザ名とパスワードを記述する。 |
12.6.2.2.4. 例外ハンドリングの実装¶
WebFaultException
に例外をラップして、スローすることを推奨している。WebFaultException
をキャッチして、その原因例外を判定してそれぞれの処理を行う。@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 の種別で例外を判定し、それぞれの処理を記述する(画面にメッセージを出す、例外をスローするなど) |
12.6.2.2.5. タイムアウトの設定¶
クライアントで指定できるタイムアウトは大きく以下の2つがある。
- SOAPサーバとのコネクションタイムアウト
- SOAPサーバへのリクエストタイムアウト
org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean
のカスタムプロパティに指定する必要がある。[client projectName]-domain/src/main/resources/META-INF/spring/[client projectName]-domain.xml
<bean id="todoWebService"
class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
<property name="serviceInterface" value="com.example.ws.todo.TodoWebService" />
<property name="serviceName" value="TodoWebService" />
<property name="portName" value="TodoWebPort" />
<property name="namespaceUri" value="http://example.com/todo" />
<property name="wsdlDocumentResource" value="${webservice.todoWebService.wsdlDocumentResource}" />
<!-- (1) -->
<property name="customProperties">
<map>
<!-- (2) -->
<entry key="com.sun.xml.internal.ws.connect.timeout" value="${webservice.connect.timeout}"/>
<entry key="com.sun.xml.internal.ws.request.timeout" value="${webservice.request.timeout}"/>
</map>
</property>
</bean>
[client projectName]-env/src/main/resources/META-INF/spring/[client projectName]-infra.properties
# (3)
webservice.request.timeout=3000
webservice.connect.timeout=3000
項番 | 説明 |
---|---|
(1)
|
customProperties プロパティにMapを指定することでカスタムプロパティを定義する。 |
(2)
|
コネクションタイムアウトとリクエストタイムアウトを定義する。
それぞれの値をプロパティファイルに切り出した場合のサンプルである。
Warning タイムアウト定義に使用するキーについて それぞれのタイムアウトを定義するキーはJAX-WSの実装により異なる値を設定する必要がある。 詳細はJAX_WS-1166 Standardize timeout settingsを参照されたい。 Note WebLogicで該当キーを指定する場合は、 指定をしない場合、WebLogicのJAX-WS実装ライブラリが
設定の方法は以下のとおりである。
|
(3)
|
[client projectName]-domain.xml で定義したプロパティのキーの値を設定する。コネクションタイムアウトとリクエストタイムアウトを記述する。 |
12.6.3. Appendix¶
12.6.3.1. SOAPサーバ用にプロジェクトの設定を変更する¶
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
12.6.3.1.1. 既存プロジェクトの変更¶
12.6.3.1.2. modelプロジェクトの作成¶
modelプロジェクトの構成について説明する。
artifactId-model
├── pom.xml ... (1)
項番
|
説明
|
---|---|
(1)
|
modelモジュールの構成を定義するPOM(Project Object Model)ファイル。 このファイルでは、以下の定義を行う。
|
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-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>
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-security-core-dependencies</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-recommended-dependencies</artifactId>
<type>pom</type>
</dependency>
<!-- == End TERASOLUNA == -->
</dependencies>
</project>
12.6.3.1.3. webserviceプロジェクトの作成¶
webserviceプロジェクトの構成について説明する。
artifactId-webservice
├── pom.xml ... (1)
項番
|
説明
|
---|---|
(1)
|
webserviceモジュールの構成を定義するPOM(Project Object Model)ファイル。 このファイルでは、以下の定義を行う。
|
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-common-dependencies</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-security-core-dependencies</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-recommended-dependencies</artifactId>
<type>pom</type>
</dependency>
<!-- == End TERASOLUNA == -->
</dependencies>
</project>
Note
webserviceプロジェクトのコンパイルのためにJAX-WS APIが必要となる。 Java SE 11環境にてJAX-WS APIを使用するにはJAX-WSの削除を参照されたい。
12.6.3.2. SOAPサーバのパッケージ構成¶
プロジェクト名 | 説明 |
---|---|
[server projectName]-domain
|
SOAPサーバのドメイン層に関するクラス・設定ファイルを格納するプロジェクト
|
[server projectName]-web
|
SOAPサーバのアプリケーション層に関するクラス・設定ファイルを格納するプロジェクト
|
[server projectName]-env
|
SOAPサーバの環境に依存するファイル等を格納するプロジェクト
|
[server projectName]-model
|
SOAPサーバのドメイン層に関するクラスの中で、Webサービス実行時に使用し、クライアントと共有するクラスを格納するプロジェクト
|
[server projectName]-webservice
|
SOAPサーバが提供するWebサービスのインターフェースを格納するプロジェクト
|
12.6.3.2.1. [server projectName]-domain¶
[server projectName]-modelの依存関係を追加するため、pom.xml
に以下を追加する。
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>artifactId-model</artifactId>
</dependency>
その他のパッケージ構成は、通常のdomainプロジェクトと変わらないため、「アプリケーションのレイヤ化 の プロジェクト構成」を参照されたい。
12.6.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
JAX-WSでは、XML電文のシリアライズにJAXBを利用しており、アプリケーションの実行にはJAXBがクラスパスに登録されている必要がある。 Java SE 11環境にてJAXBをクラスパスに登録するにはJAXBの削除を参照されたい。
なお、「Tomcat上でのWebサービス開発」で解説するApache CXFを利用する場合は、JAXBが自動的に依存関係に追加されることを確認している。
[server projectName]-webのプロジェクト推奨構成を、以下に示す。
[server projectName]-web
└src
└main
├java
│ └com
│ └example
│ ├app...(1)
│ └ws...(2)
│ ├exception...(3)
│ │ └WsExceptionHandler.java
│ ├abc
│ │ └AbcWebServiceImpl.java
│ └def
│ └DefWebServiceImpl.java
├resources
│ ├META-INF
│ │ └spring
│ │ ├applicationContext.xml...(4)
│ │ ├application.properties...(5)
│ │ ├spring-mvc.xml ...(6)
│ │ ├spring-security.xml...(7)
│ │ └[server projectName]-ws.xml...(8)
│ └i18n
│ └application-messages.properties...(9)
└webapp
├resources...(10)
└WEB-INF
├views ...(11)
└web.xml...(12)
項番 | 説明 |
---|---|
(1)
|
アプリケーション層の構成要素を格納するパッケージ。
Webサービスのみ作成する場合は削除してもよい。
|
(2)
|
Webサービスの関連クラスを格納するパッケージ。
|
(3)
|
Webサービスの例外ハンドラーなどを格納するパッケージ。
|
(4)
|
アプリケーション全体に関するBean定義を行う。
|
(5)
|
アプリケーションで使用するプロパティを定義する。
|
(6)
|
Spring MVCの設定を行うBean定義を行う。
Webサービスのみ作成する場合は削除してもよい。
|
(7)
|
Spring Securityの設定を行うBean定義を行う。
|
(8)
|
Webサービスに関するBean定義を行う。
|
(9)
|
画面表示用のメッセージ(国際化対応)定義を行う。
|
(10)
|
静的リソース(css、js、画像など)を格納する。
Webサービスのみ作成する場合は削除してもよい。
|
(11)
|
View(jsp)を格納する。
Webサービスのみ作成する場合は削除してもよい。
|
(12)
|
Servletのデプロイメント定義を行う。
|
Note
SOAPサーバの不要なファイル
SOAPサーバで、Webサービスのみを作成する場合、ブランクプロジェクトに存在するSpring MVCの設定ファイルなどは不要となるため、削除したほうが望ましい。
12.6.3.2.3. [server projectName]-env¶
[server projectName]-envについては、通常のenvプロジェクトと変わらないため、「アプリケーションのレイヤ化 の プロジェクト構成」を参照されたい。
12.6.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サービス実行時に使用するクラスを格納するパッケージ。
|
12.6.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を格納するパッケージ。
|
12.6.3.3. クライアントのパッケージ構成¶
プロジェクト名 | 説明 |
---|---|
[client projectName]-domain
|
クライアントのドメイン層に関するクラス・設定ファイルを格納するプロジェクト
|
[client projectName]-web
|
クライアントのアプリケーション層に関するクラス・設定ファイルを格納するプロジェクト
|
[client projectName]-env
|
クライアントの環境に依存するファイル等を格納するプロジェクト
|
Note
[server projectName]-modelと[server projectName]-webserviceについては、前述の「 SOAPサーバのパッケージ構成」を参照されたい。
12.6.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プロジェクトと変わらないため、「アプリケーションのレイヤ化 の プロジェクト構成」を参照されたい。
12.6.3.3.2. [client projectName]-web¶
[client projectName]-webについては、通常のwebプロジェクトと変わらないため、「アプリケーションのレイヤ化 の プロジェクト構成」を参照されたい。
12.6.3.3.3. [client projectName]-env¶
[client projectName]-envのプロジェクト推奨構成を、以下に示す。
[projectName]-env
├configs ...(1)
│ └[envName] ...(2)
│ └resources ...(3)
└src
└main
└resources ...(4)
├META-INF
│ └spring
│ ├[projectName]-env.xml ...(5)
│ └[projectName]-infra.properties ...(6)
└logback.xml ...(7)
項番 | 説明 |
---|---|
(1)
|
全環境の環境依存ファイルを管理するためのディレクトリ。
|
(2)
|
環境毎の環境依存ファイルを管理するためのディレクトリ。
ディレクトリ名は、環境を識別する名前を指定する。
|
(3)
|
環境毎の設定ファイルを管理するためのディレクトリ。
サブディレクトリの構成や管理する設定ファイルは、(4)と同様。
|
(4)
|
ローカル開発環境用の設定ファイルを管理するためのディレクトリ。
|
(5)
|
ローカル開発環境用のBean定義を行う。
|
(6)
|
ローカル開発環境用のプロパティを定義する。
WSDLのURLなど環境ごとに変更の可能性がある値を設定する。
|
(7)
|
ローカル開発環境用のログ出力定義を行う。
|
12.6.3.4. wsimportについて¶
12.6.3.4.1. wsimportの使い道¶
12.6.3.4.2. wsimportの使い方¶
# (1)
wsimport -keep -p [出力するソースのパッケージ名] -s [出力するソースを格納する場所] [wsdlのURL]
項番 | 説明 |
---|---|
(1)
|
wsimportの引数としてWSDLのURLを指定する。
オプションとして以下を使用する。
その他オプションについては、Java Platform, Standard Edition Tools Reference -Web Services(wsimport)-を参照されたい。
|
Note
wsimportはデフォルトの挙動としてclassファイルのみが出力される。動かすだけなら問題はないが、デバッグなどを実行したい場合に備えkeepオプションを付けてソースも保存することを推奨する。
例えば、以下のようなコマンドとなる。
wsimport -keep -p com.example.ws.todo -s c:/tmp http://AAA.BBB.CCC.DDD:XXXX/soap-web/ws/TodoWebService?wsdl
作成されるソースは公開されているWebサービスに依存するが、本ガイドラインで使用している以下のJavaクラスが出力される。
- Webサービスインターフェース(ソース例では
TodoWebService.java
) - Domain Object(ソース例では
Todo.java
)
Note
出力されるJavaクラスは上記以外にも出力される。出力されたソースのみでクライアントを作成可能なソースである。ただし、本ガイドラインではクライアントは、org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean
を使用する方針であるため、その他のJavaクラスは使用しないことを推奨する。
12.6.3.5. Tomcat上でのWebサービス開発¶
CXFServlet
を使用する必要がある。- POJOでWebサービス実装クラスを記述する方式
SpringBeanAutowiringSupport
を継承してWebサービス実装クラスを作成する方式 (これまで説明してきた方法)
12.6.3.5.1. CXFServletを使用する場合の設定¶
CXFServlet
を使用するため、pom.xml
にライブラリの設定を記述する。
<!-- (1) -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.1.4</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.1.4</version>
</dependency>
項番 | 説明 |
---|---|
(1)
|
CXFServlet を使用するため、Apache CXFライブラリへの依存関係を追加する。 |
Warning
Java SE 11環境にてCXFServletを利用する場合
Java SE 11でJAX-WSが削除されたことに伴い、関連技術であるSAAJも削除された。 CXFServletでは内部的にSAAJを使用しているため、SAAJの不足により期待しない挙動となることが確認されている。 具体的には、SOAP Faultの処理が正常に行なわれず、Webサービスのエラー時にSOAPクライアントに意図しない例外メッセージが返却される。
このため、CXFServletの実行時にはSAAJがクラスパスに登録されている必要がある。 Java SE 11では以下のようにsaaj-implを依存関係に追加する必要があるが、APサーバから提供される場合はこの限りではない。 例として、JBoss 7.2を利用する場合、JBossによりSAAJが提供されることを確認している。
<dependency>
<groupId>com.sun.xml.messaging.saaj</groupId>
<artifactId>saaj-impl</artifactId>
</dependency>
saaj-impl以外のJava SE 11環境にてJAX-WSを利用する場合に必要となる設定はJAX-WSの削除を参照されたい。
Note
saaj-implのバージョンはterasoluna-gfw-parentが依存しているSpring Bootで管理されているため、pom.xmlでのバージョンの指定は不要である。
次にweb.xml
にSOAP Web Serviceを受け付けるCXFServlet
を定義する。
<!-- (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 のパスを指定する。 |
(2)
|
定義したサーブレットへのマッピングを定義する。この場合、コンテキスト名/ws配下にWebサービスが作成される。
|
12.6.3.5.2. POJO方式で必要な設定¶
Webサービス実装クラスをエンドポイントとして設定する。
[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="#todoWebServiceImpl"
address="/TodoWebService" />
</beans>
項番 | 説明 |
---|---|
(1)
|
公開するエンドポイントを定義する。
implementor 属性に、DIコンテナに登録済みのWebサービスクラスのbean名(「#bean名」形式)を指定する。address 属性にWebサービスを公開するアドレスを指定する。アドレスは、公開するエンドポイントのパス部分のみ記述する。
属性の詳細についてはApache CXF JAX-WS Configurationを参照されたい。
|
TodoWebServiceImpl
をPOJOとして作成する。
[server projectName]-web/src/main/java/com/example/ws/todo/TodoWebServiceImpl.java
package com.example.ws.todo;
import java.util.List;
import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.xml.ws.BindingType;
import javax.xml.ws.soap.SOAPBinding;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import org.springframework.stereotype.Component;
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;
// (1)
@Component
@WebService(
portName = "TodoWebPort",
serviceName = "TodoWebService",
targetNamespace = "http://example.com/todo",
endpointInterface = "com.example.ws.todo.TodoWebService")
@BindingType(SOAPBinding.SOAP12HTTP_BINDING)
// (2)
public class TodoWebServiceImpl implements TodoWebService {
// omitted
}
項番 | 説明 |
---|---|
(1)
|
@Component を付けて、DIコンテナへの登録を行う。 |
(2)
|
コンポーネントスキャンにてDIコンテナへの登録が可能であるため、POJOとして作成する。つまり、
org.springframework.web.context.support.SpringBeanAutowiringSupport を継承する必要がなくなる。 |
12.6.3.5.3. SpringBeanAutowiringSupportを継承する方式で必要な設定¶
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)
|
公開するエンドポイントを定義する。
implementor 属性に公開するWebサービスの実装クラスを指定する。address 属性にWebサービスを公開するアドレスを指定する。アドレスは、公開するエンドポイントのパス部分のみ記述する。
属性の詳細についてはApache CXF JAX-WS Configurationを参照されたい。
|