SOAP Web サービスの生成

このガイドでは、Spring を使用して SOAP ベースの Web サービスサーバーを作成するプロセスについて説明します。

構築するもの

WSDL ベースの SOAP Web サービスを使用して、ヨーロッパのさまざまな国からのデータを公開するサーバーを構築します。

この例を単純化するために、英国、スペイン、ポーランドのハードコードされたデータを使用します。

必要なもの

本ガイドの完成までの流れ

ほとんどの Spring 入門ガイドと同様に、最初から始めて各ステップを完了するか、すでに慣れている場合は基本的なセットアップステップをバイパスできます。いずれにしても、最終的に動作するコードになります。

最初から始めるには、Spring Initializr から開始に進みます。

基本スキップするには、次の手順を実行します。

完了したときは、gs-soap-service/complete のコードに対して結果を確認できます。

Spring Initializr から開始

IDE を使用する場合はプロジェクト作成ウィザードを使用します。IDE を使用せずにコマンドラインなどで開発する場合は、この事前に初期化されたプロジェクトからプロジェクトを ZIP ファイルとしてダウンロードできます。このプロジェクトは、このチュートリアルの例に合うように構成されています。

プロジェクトを手動で初期化するには:

  1. IDE のメニューまたはブラウザーから Spring Initializr を開きます。アプリケーションに必要なすべての依存関係を取り込み、ほとんどのセットアップを行います。

  2. Gradle または Maven のいずれかと、使用する言語を選択します。このガイドは、Java を選択したことを前提としています。

  3. 依存関係をクリックし、Spring WebSpring Web Services を選択します。

  4. 生成をクリックします。

  5. 結果の ZIP ファイルをダウンロードします。これは、選択して構成された Web アプリケーションのアーカイブです。

EclipseIntelliJ のような IDE は新規プロジェクト作成ウィザードから Spring Initializr の機能が使用できるため、手動での ZIP ファイルのダウンロードやインポートは不要です。
pom.xml ファイルと build.gradle ファイルの両方に追加のビルド情報が必要です。これは、次の手順で追加します。
プロジェクトを Github からフォークして、IDE または他のエディターで開くこともできます。

Spring-WS 依存関係を追加する

プロジェクトには、ビルドファイルの依存関係として spring-ws-core と wsdl4j を含める必要があります。

次の例は、Maven を使用する場合に pom.xml ファイルに加える必要がある変更を示しています。

<dependency>
	<groupId>wsdl4j</groupId>
	<artifactId>wsdl4j</artifactId>
</dependency>

次の例は、Gradle を使用する場合に build.gradle ファイルに加える必要がある変更を示しています。

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-web-services'
	implementation 'wsdl4j:wsdl4j'
	jaxb("org.glassfish.jaxb:jaxb-xjc")
	testImplementation('org.springframework.boot:spring-boot-starter-test')
}

ドメインを定義する XML スキーマを作成する

Web サービスドメインは、Spring-WS が WSDL として自動的にエクスポートする XML スキーマファイル(XSD)で定義されます。

国の namepopulationcapitalcurrency を返す操作を含む XSD ファイルを作成します。次のリスト(src/main/resources/countries.xsd から)は、必要な XSD ファイルを示しています。

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://spring.io/guides/gs-producing-web-service"
           targetNamespace="http://spring.io/guides/gs-producing-web-service" elementFormDefault="qualified">

    <xs:element name="getCountryRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="name" type="xs:string"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="getCountryResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="country" type="tns:country"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="country">
        <xs:sequence>
            <xs:element name="name" type="xs:string"/>
            <xs:element name="population" type="xs:int"/>
            <xs:element name="capital" type="xs:string"/>
            <xs:element name="currency" type="tns:currency"/>
        </xs:sequence>
    </xs:complexType>

    <xs:simpleType name="currency">
        <xs:restriction base="xs:string">
            <xs:enumeration value="GBP"/>
            <xs:enumeration value="EUR"/>
            <xs:enumeration value="PLN"/>
        </xs:restriction>
    </xs:simpleType>
</xs:schema>

XML スキーマに基づいてドメインクラスを生成する

次のステップは、XSD ファイルから Java クラスを生成することです。適切なアプローチは、Maven または Gradle プラグインを使用して、ビルド時にこれを自動的に行うことです。

以下のリストは、Maven に必要なプラグイン構成を示しています。

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>jaxb2-maven-plugin</artifactId>
	<version>3.1.0</version>
	<executions>
		<execution>
			<id>xjc</id>
			<goals>
				<goal>xjc</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<sources>
			<source>${project.basedir}/src/main/resources/countries.xsd</source>
		</sources>
	</configuration>
</plugin>

生成されたクラスは、target/generated-sources/jaxb/ ディレクトリに配置されます。

Gradle で同じことを行うには、次のように、最初にビルドファイルで JAXB を構成する必要があります。

configurations {
	jaxb
}

bootJar {
	archiveBaseName = 'gs-producing-web-service'
	archiveVersion =  '0.1.0'
}
ビルドファイルには tag および end コメントがあります。これらのタグを使用すると、詳細な説明のためにこのガイドにその一部を簡単に抽出できます。独自のビルドファイルにこれらのコメントは必要ありません。

次のステップは、Gradle が Java クラスを生成するために使用する genJaxb タスクを追加することです。build/generated-sources/jaxb でこれらの生成された Java クラスを検索し、compileJava タスクの依存関係として genJaxb を追加するように、gradle を構成する必要があります。次のリストは、必要な追加を示しています。

sourceSets {
	main {
		java {
			srcDir 'src/main/java'
			srcDir 'build/generated-sources/jaxb'
		}
	}
}

task genJaxb {
	ext.sourcesDir = "${buildDir}/generated-sources/jaxb"
	ext.schema = "src/main/resources/countries.xsd"

	outputs.dir sourcesDir

	doLast() {
		project.ant {
			taskdef name: "xjc", classname: "com.sun.tools.xjc.XJCTask",
					classpath: configurations.jaxb.asPath
			mkdir(dir: sourcesDir)

			xjc(destdir: sourcesDir, schema: schema) {
				arg(value: "-wsdl")
				produces(dir: sourcesDir, includes: "**/*.java")
			}
		}
	}
}

compileJava.dependsOn genJaxb

Gradle には(まだ)JAXB プラグインがないため、Ant タスクが含まれ、Maven よりも少し複雑になります。

どちらの場合も、JAXB ドメインオブジェクト生成プロセスはビルドツールのライフサイクルに組み込まれているため、追加の手順は必要ありません。

カントリーリポジトリの作成

Web サービスにデータを提供するには、国のリポジトリを作成します。このガイドでは、ハードコードされたデータを使用してダミーの国リポジトリの実装を作成します。次のリスト(src/main/java/com/example/producingwebservice/CountryRepository.java から)は、その方法を示しています。

package com.example.producingwebservice;

import jakarta.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

import io.spring.guides.gs_producing_web_service.Country;
import io.spring.guides.gs_producing_web_service.Currency;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

@Component
public class CountryRepository {
	private static final Map<String, Country> countries = new HashMap<>();

	@PostConstruct
	public void initData() {
		Country spain = new Country();
		spain.setName("Spain");
		spain.setCapital("Madrid");
		spain.setCurrency(Currency.EUR);
		spain.setPopulation(46704314);

		countries.put(spain.getName(), spain);

		Country poland = new Country();
		poland.setName("Poland");
		poland.setCapital("Warsaw");
		poland.setCurrency(Currency.PLN);
		poland.setPopulation(38186860);

		countries.put(poland.getName(), poland);

		Country uk = new Country();
		uk.setName("United Kingdom");
		uk.setCapital("London");
		uk.setCurrency(Currency.GBP);
		uk.setPopulation(63705000);

		countries.put(uk.getName(), uk);
	}

	public Country findCountry(String name) {
		Assert.notNull(name, "The country's name must not be null");
		return countries.get(name);
	}
}

カントリーサービスエンドポイントの作成

サービスエンドポイントを作成するには、いくつかの Spring WS アノテーションを持つ POJO だけで、受信 SOAP リクエストを処理する必要があります。次のリスト(src/main/java/com/example/producingwebservice/CountryEndpoint.java から)は、そのようなクラスを示しています。

package com.example.producingwebservice;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;

import io.spring.guides.gs_producing_web_service.GetCountryRequest;
import io.spring.guides.gs_producing_web_service.GetCountryResponse;

@Endpoint
public class CountryEndpoint {
	private static final String NAMESPACE_URI = "http://spring.io/guides/gs-producing-web-service";

	private CountryRepository countryRepository;

	@Autowired
	public CountryEndpoint(CountryRepository countryRepository) {
		this.countryRepository = countryRepository;
	}

	@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest")
	@ResponsePayload
	public GetCountryResponse getCountry(@RequestPayload GetCountryRequest request) {
		GetCountryResponse response = new GetCountryResponse();
		response.setCountry(countryRepository.findCountry(request.getName()));

		return response;
	}
}

@Endpoint (Javadoc) アノテーションは、受信 SOAP メッセージを処理するための潜在的な候補として Spring WS にクラスを登録します。

Spring WS は @PayloadRoot (Javadoc) アノテーションを使用して、メッセージの namespace および localPart に基づいてハンドラーメソッドを選択します。

@RequestPayload (Javadoc) アノテーションは、受信メッセージがメソッドの request パラメーターにマッピングされることを示します。

@ResponsePayload (Javadoc) アノテーションは、Spring WS に戻り値をレスポンスペイロードにマップさせます。

これらすべてのコードチャンクで、WSDL に基づいてドメインクラスを生成するタスクを実行しない限り、io.spring.guides クラスは IDE でコンパイル時エラーを報告します。

Web サービス Bean の構成

次のリスト(src/main/java/com/example/producingwebservice/WebServiceConfig.java から)が示すように、Spring WS 関連の Bean 構成を使用して新しいクラスを作成します。

package com.example.producingwebservice;

import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;

@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
	@Bean
	public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
		MessageDispatcherServlet servlet = new MessageDispatcherServlet();
		servlet.setApplicationContext(applicationContext);
		servlet.setTransformWsdlLocations(true);
		return new ServletRegistrationBean<>(servlet, "/ws/*");
	}

	@Bean(name = "countries")
	public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
		DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
		wsdl11Definition.setPortTypeName("CountriesPort");
		wsdl11Definition.setLocationUri("/ws");
		wsdl11Definition.setTargetNamespace("http://spring.io/guides/gs-producing-web-service");
		wsdl11Definition.setSchema(countriesSchema);
		return wsdl11Definition;
	}

	@Bean
	public XsdSchema countriesSchema() {
		return new SimpleXsdSchema(new ClassPathResource("countries.xsd"));
	}
}
MessageDispatcherServlet (Javadoc) および DefaultWsdl11Definition (Javadoc) に Bean 名を指定する必要があります。Bean 名は、Web サービスと生成された WSDL ファイルが使用可能な URL を決定します。この場合、WSDL は http://<host>:<port>/ws/countries.wsdl (英語) で使用可能になります。

この構成では、WSDL ロケーションサーブレット変換 servlet.setTransformWsdlLocations(true) も使用します。http://localhost:8080/ws/countries.wsdl にアクセスすると、soap:address には適切なアドレスがあります。代わりに、マシンに割り当てられているパブリック IP アドレスから WSDL にアクセスすると、代わりにそのアドレスが表示されます。

アプリケーションを実行可能にする

Spring Boot は、アプリケーションクラスを作成します。この場合、さらに変更する必要はありません。これを使用して、このアプリケーションを実行できます。次のリスト(src/main/java/com/example/producingwebservice/ProducingWebServiceApplication.java から)は、アプリケーションクラスを示しています。

package com.example.producingwebservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ProducingWebServiceApplication {

	public static void main(String[] args) {
		SpringApplication.run(ProducingWebServiceApplication.class, args);
	}
}

@SpringBootApplication は、次のすべてを追加する便利なアノテーションです。

  • @Configuration: アプリケーションコンテキストの Bean 定義のソースとしてクラスにタグを付けます。

  • @EnableAutoConfiguration: クラスパス設定、他の Bean、さまざまなプロパティ設定に基づいて Bean の追加を開始するよう Spring Boot に指示します。例: spring-webmvc がクラスパスにある場合、このアノテーションはアプリケーションに Web アプリケーションとしてフラグを立て、DispatcherServlet のセットアップなどの主要な動作をアクティブにします。

  • @ComponentScan: Spring に、com/example パッケージ内の他のコンポーネント、構成、サービスを探して、コントローラーを検出させるように指示します。

main() メソッドは、Spring Boot の SpringApplication.run() メソッドを使用してアプリケーションを起動します。XML が 1 行もないことに気付きましたか? web.xml ファイルもありません。この Web アプリケーションは 100% 純粋な Java であり、接続機能やインフラストラクチャの構成に対処する必要はありませんでした。

実行可能 JAR を構築する

コマンドラインから Gradle または Maven を使用してアプリケーションを実行できます。必要なすべての依存関係、クラス、リソースを含む単一の実行可能 JAR ファイルを構築して実行することもできます。実行可能な jar を構築すると、開発ライフサイクル全体、さまざまな環境などで、アプリケーションとしてサービスを簡単に提供、バージョン管理、デプロイできます。

Gradle を使用する場合、./gradlew bootRun を使用してアプリケーションを実行できます。または、次のように、./gradlew build を使用して JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar build/libs/gs-soap-service-0.1.0.jar

Maven を使用する場合、./mvnw spring-boot:run を使用してアプリケーションを実行できます。または、次のように、./mvnw clean package で JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar target/gs-soap-service-0.1.0.jar
ここで説明する手順は、実行可能な JAR を作成します。クラシック WAR ファイルを作成することもできます。

ロギング出力が表示されます。サービスは数秒以内に起動して実行されるはずです。

アプリケーションをテストする

アプリケーションが実行されたため、テストできます。次の SOAP リクエストを含む request.xml というファイルを作成します。

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
				  xmlns:gs="http://spring.io/guides/gs-producing-web-service">
   <soapenv:Header/>
   <soapenv:Body>
      <gs:getCountryRequest>
         <gs:name>Spain</gs:name>
      </gs:getCountryRequest>
   </soapenv:Body>
</soapenv:Envelope>

SOAP インターフェースのテストに関しては、いくつかのオプションがあります。*nix/Mac システムを使用している場合は、SoapUI (英語) に似たものを使用するか、コマンドラインツールを使用できます。次の例では、コマンドラインから curl を使用しています。

# Use data from file
curl --header "content-type: text/xml" -d @request.xml http://localhost:8080/ws
# Use inline XML data
curl <<-EOF -fsSL -H "content-type: text/xml" -d @- http://localhost:8080/ws \
  > target/response.xml && xmllint --format target/response.xml

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                                  xmlns:gs="http://spring.io/guides/gs-producing-web-service">
   <soapenv:Header/>
   <soapenv:Body>
      <gs:getCountryRequest>
         <gs:name>Spain</gs:name>
      </gs:getCountryRequest>
   </soapenv:Body>
</soapenv:Envelope>

EOF

その結果、次のレスポンスが表示されます。

<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <SOAP-ENV:Body>
    <ns2:getCountryResponse xmlns:ns2="http://spring.io/guides/gs-producing-web-service">
      <ns2:country>
        <ns2:name>Spain</ns2:name>
        <ns2:population>46704314</ns2:population>
        <ns2:capital>Madrid</ns2:capital>
        <ns2:currency>EUR</ns2:currency>
      </ns2:country>
    </ns2:getCountryResponse>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
奇妙なことに、出力は、上に示した適切にフォーマットされた XML ドキュメントではなく、コンパクトな XML ドキュメントになります。システムに xmllib2 がインストールされている場合は、curl -fsSL --header "content-type: text/xml" -d @request.xml http://localhost:8080/ws > output.xml and xmllint --format output.xml で結果が適切にフォーマットされていることを確認できます。

要約

おめでとう! Spring Web Services を使用して SOAP ベースのサービスを開発しました。

関連事項

次のガイドも役立ちます。

新しいガイドを作成したり、既存のガイドに貢献したいですか? 投稿ガイドラインを参照してください [GitHub] (英語)

すべてのガイドは、コード用の ASLv2 ライセンス、およびドキュメント用の Attribution、NoDerivatives creative commons ライセンス (英語) でリリースされています。

コードを入手する