Spring Web ServicesによるWebサービスの作成(前編)

最終更新日:2007/11/12

目次

Spring Web Servicesとは?

Spring Web Servicesとは、ドキュメントドリブンでWebサービスを開発するためのプロダクトです。Spring Frameworkのサブプロジェクトとして開発されており、2007/08に1.0がリリースされました。本稿執筆時点(2007/11/01)の最新版は1.0.1です。

Contract-First

Spring Web Servicesでは、実際にやりとりするXMLのスキーマ(XML Schema)を定義し、それを元に開発を行う手法(Contract-First)を採用しています。JAX-WSのように、Webサービスとして公開するクラスやメソッドにアノテーションを付与する方法とは、開発スタイルそのものが異なっています。

こういった開発手法を採用している理由は、オブジェクトとXMLのインピーダンスミスマッチです。XML Schemaの作成は簡単ではないため、JavaのオブジェクトからXML Schemaを生成してくれれば楽になりますが、その方法では例えば以下のような問題があります。

想定通りのXML Schemaが生成されるとは限らない
Javaオブジェクトのフィールドを<xsd:all>(要素を順不同に出現させる)で表現して欲しいが、実際には<xsd:sequence>(要素を順番に出現させる)で表現されてしまう、といった場合がある
XML Schemaの再利用ができない
複数のXML Schemaで共通する部分があれば、共通部分を抜き出してincludeやimportすることにより再利用できますが、Javaオブジェクトから自動生成する場合は難しい

一番の問題は入出力のXML(スキーマ)が実装に依存することでしょう。入出力のXML(スキーマ)はサービスにとって重要なContract(契約)であるため、それが実装に依存したり、実装の変更に直接影響を受けてしまうのは確かに問題です。こういった理由でSpring Web Servicesでは、始めにXML Schemaを作成する開発スタイルをサポートしています。

主な特徴

以下はSpring Web Servicesのドキュメントからの抜粋です。

XML APIのサポート
DOM, SAX, StAX,, JDOM, dom4jといったXML APIのサポート
柔軟なXMLマーシャリング
Object/XMLマッピングとしてJAXB, Castor, XMLBeans, JiBX,, XStreamをサポート
Springの知識を生かせる
Spring Web Servicesの設定はSpringのapplicationContext.xmlに記述するので、Springの知識があれば理解しやすい
WS-Securityのサポート
SOAPメッセージに対する署名、暗号化がサポートされている

今回作成するWebサービス

ホテル検索サービス

指定の宿泊期間に対して、空室のあるホテル情報を返却するサービスを作成することにします。といっても、肝心なのはそれをWebサービスとして公開する部分ですので、サービスの実装は決めうちの情報を返すだけにします。

検索ロジックの実装

検索ロジックの実装はSpring管理下のBeanが行うことにします。具体的には以下のインタフェースを実装するBeanが検索処理を行い、結果をJavaのオブジェクトとして返します。

/**
 * ホテル検索インタフェース
 */
public interface IHotelSeacher {

    /**
     * 指定の範囲で空室のあるホテル一覧を検索する。
     * 
     * @param startDate 検索開始日時
     * @param endDate 検索終了日時
     * @return ホテル一覧
     */
    List<HotelInformation> search(Date startDate, Date endDate);
}

searchメソッドの戻り値は、以下のJavaBeanです。

/**
 * ホテル情報
 */
public class HotelInformation {

    /** ホテル名 */
    private String name;
    /** 住所 */
    private String address;
    /** 空室数 */
    private int emptyRoomNum;

    public HotelInformation() {}

    public HotelInformation(String name, String address, int emptyRoomNum) {
        this.name = name;
        this.address = address;
        this.emptyRoomNum = emptyRoomNum;
    }

    // Getter/Setterは省略
}

従って、これから作成するWebサービスはSOAPメッセージの送受信と、バックエンドのBeanを呼び出すことが主な役割になります。

準備

以下のプロダクトを用意します。

Spring Web ServicesのチュートリアルではMavenを使って依存する全てのJARを取得する方法が書かれています。Mavenを使ってもよいですが、Mavenをインストールする必要があること、Mavenによって取得されるJARが必ずしも最新でないということ等を考慮して、今回は必要なプロダクト(上記で挙げたもの)をそれぞれダウンロードして利用することにします。

EclipseやSpring IDEは開発環境ですので必須ではありませんが、これらを使うことを前提とします。Spring IDEのセットアップの方法については、Spring 2.0によるDBアプリケーションの作成 番外編を参照下さい。

JDK は5.0でもよいですが、6.0ならばSAAJ(SOAP with Attachments API for Java)が標準APIとして追加されているので、余計なJARを取得する手間が省けます。ということで、6.0を使用します。5.0で動作させる場合は必要なJARを別途ダウンロードして下さい。

尚、JAXB 2.0もJava6の標準APIですので本来ならば入手する必要はありませんが、後に使用するJAXBのAntタスクは含まれていないため、Antタスクを実行するためにJAXB 2.0もダウンロードします(実行時のクラスパスに含める必要はありません)。

Springプロジェクトの作成

プロジェクトの作成と構成

Eclipseで新規にSpringプロジェクト(Spring IDEプラグインをインストールすると、メニューに出てきます)を作成し、以下のようなフォルダ構成を作成します。

ビルドパスの設定

次のJARをweb/WEB-INF/libフォルダへコピーし、プロジェクトのビルドパスに追加します。

プロダクト名 JARファイル JARの格納場所
Spring Framework spring.jar dist/
Spring Framework commons-logging.jar lib/jakarta-commons/
Spring Framework wsdl4j.jar lib/axis/
Spring Web Services spring-oxm-1.0.1.jar /
Spring Web Services spring-oxm-tiger-1.0.1.jar /
Spring Web Services spring-ws-core-1.0.1.jar /
Spring Web Services spring-ws-core-tiger-1.0.1.jar /
Spring Web Services spring-xml-1.0.1.jar /

検索ロジックの作成

上述のIHotelSeacherインタフェースの実装クラスを作ります。といっても単なるスタブで、決めうちのオブジェクトを返すだけです。

package service;

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

import bean.HotelInformation;

public class HotelSearcherStub implements IHotelSeacher {

    @Override
    public List<HotelInformation> search(Date startDate, Date endDate) {
        List<HotelInformation> hotels = new ArrayList<HotelInformation>();

        hotels.add(new HotelInformation("Aホテル", "埼玉県蕨市xxx", 10));
        hotels.add(new HotelInformation("Bホテル", "東京都北区xxx", 5));
        hotels.add(new HotelInformation("Cホテル", "埼玉県川口市xxx", 23));

        return hotels;
    }
}

新規にserviceパッケージを作り、インタフェースと共に格納します。HotelInformationクラスも作成し、beanパッケージを作成してそこへ格納します。また、このBeanをSpring管理下のBeanとするために、Bean定義ファイルを次のように作成します。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="HotelSeacher" class="service.HotelSearcherStub"/>
</beans>

Bean定義ファイルの名前は、spring-ws-servlet.xml とします。この名前の付け方には意味があります。任意の名前を付けることができますが、ひとまずこの名前で作成します。

XML Schemaの作成

入出力のXMLサンプル

いきなりXML Schemaを作るのは大変なので、まずは入出力のXMLサンプルを考えてみます。

まずは入力のXMLサンプルです。<startDate>, <endDate>で検索開始日と終了日を指定します。Spring Web ServicesではやりとりするXMLに名前空間が必要になる(省略するとエラーが発生する)ため、名前空間宣言も記述しています。

<searchRequest xmlns="http://www.oki.com/hotelservice/schemas">
    <startDate>2007-10-03</startDate>
    <endDate>2007-10-07</endDate>
</searchRequest>

次は出力のXMLサンプルです。<hotel>はヒットしたホテルの詳細情報です。

<searchResponse xmlns="http://www.oki.com/hotelservice/schemas">
    <hotel>
        <name>Aホテル</name>
        <address>埼玉県蕨市xxx</address>
        <emptyRoomNum>10</emptyRoomNum>
    </hotel>
    <hotel>
        (省略)
    </hotel>
    ・・・
</searchResponse>

XML Schemaの作成方法

さて、このようなXMLに対するXML Schemaを作成するわけですが、一から作るのはやはり大変です。何かしらXML Schema作成をサポートしてくれるツールを使いたいところです。

XMLからスキーマを生成するツールを使う方法もありますが、今回は後のXML⇔オブジェクトマッピングを自動化することも考えて、JavaクラスからXML Schemaを生成することにします。

つまり、純粋なContract-Firstのアプローチではありません。入出力の仕様を重視するなら、XML SchemaとJavaのオブジェクトは切り離して考えるべきですが、その分マッピングは複雑になります。今回はサンプルということもあり、手軽な方法で作ってみることにします。

JAXBの使用

JavaクラスからXML Schemaを生成するためにJAXBを使います。前出の通り、JAXBはJava6の標準APIであり、Java6環境ならばJARを追加することなくそのまま使えます。Java5以前の環境で動作させる場合は別途JARを入手することにより使用できます。このJAXBは後のXML⇔オブジェクトマッピングでも使用します。

XMLを表現するJavaBeanの作成

XML Schemaを生成するために、上記で示したXMLサンプルの要素毎にクラスを作成します。作成する必要があるのは、<searchRequest>, <searchResponse>, <hotel>の3つです(基本的には子要素を持つ要素に対してクラスを割り当てます)。このうち、<hotel>は先ほど作ったHotelInformationで代用できるので、残る2つを新規に作成します。

SearchRequest.java

@XmlRootElement
@XmlType(name = "searchRequestType", propOrder = {"startDate", "endDate"})
public class SearchRequest {

    private Date startDate;
    private Date endDate;

    @XmlElement(required = true)
    public Date getStartDate() {
        return startDate;
    }

    public void setStartDate(Date startDate) {
        this.startDate = startDate;
    }

    @XmlElement(required = true)
    public Date getEndDate() {
        return endDate;
    }

    public void setEndDate(Date endDate) {
        this.endDate = endDate;
    }
}

startDate, endDateというフィールドとそのGetter/Setterを持つJavaBeanにアノテーションが色々付いています。このアノテーションはJAXBのアノテーションで、XML Schema生成やXML⇔オブジェクトマッピングで使われます。

@XmlRootElementはこのクラスがXML Schemaのグローバル要素にマッピングされることを示すものです。<searchRequest>要素はリクエストのルート要素であるため、クラス宣言に追加します。

@XmlTypeは要素の型定義を行うものです。これは必須ではなく、@XmlTypeが無くとも型定義を作ってくれますが、デフォルトではフィールドの出現順序がフィールド名の昇順で定義されたり、型定義の名前がそれとわかりにくかったりするため、XML Schema生成の補助情報として記述しています。propOrder属性ではフィールドの出現順序を定義します。

@XmlElementもXML Schema生成の補助情報として記述しています。デフォルトではObject型のフィールドはminOccurs=0、つまり省略可能な要素として宣言されるため、それを防ぐために@XmlElementアノテーションをフィールドのGetterメソッドに付与し、required属性をtrueにしています。これでminOccurs=0の記述が生成されるXML Schemaから無くなります。

SearchResponse.java

@XmlRootElement
@XmlType(name = "searchResponseType")
public class SearchResponse {

    private List<HotelInformation> hotels;

    @XmlElement(name = "hotel")
    public List<HotelInformation> getHotels() {
        return hotels;
    }

    public void setHotels(List<HotelInformation> hotels) {
        this.hotels = hotels;
    }
}

<searchResponse>の子要素は<hotel>要素のリストであったため、それに対応するクラスでは<hotel>要素に対応するクラス(HotelInformation)をListで保持します。

このクラスでもSearchRequest.javaと同様にアノテーションを付与しています。getHotels()メソッドでは@XmlElementを付与し、name属性の値を"hotel"にしています。メソッド名がgetHotelsであるため、デフォルトでは<hotels>という要素のリストが作られてしまうためです。Javaの実装とXML Schemaのミスマッチを埋めるために、このようなアノテーションを付けています。

最後にHotelInformationクラスです。

HotelInformation.java

/**
 * ホテル情報
 */
@XmlType(name = "hotelType", propOrder = {"name", "address", "emptyRoomNum"})
public class HotelInformation {

    /** ホテル名 */
    private String name;
    /** 住所 */
    private String address;
    /** 空室数 */
    private int emptyRoomNum;

    public HotelInformation() {}

    public HotelInformation(String name, String address, int emptyRoomNum) {
        this.name = name;
        this.address = address;
        this.emptyRoomNum = emptyRoomNum;
    }

    @XmlElement(required = true)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @XmlElement(required = true)
    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @XmlElement(required = true)
    public int getEmptyRoomNum() {
        return emptyRoomNum;
    }

    public void setEmptyRoomNum(int emptyRoomNum) {
        this.emptyRoomNum = emptyRoomNum;
    }
}

前出のコードにアノテーションを付与しただけです。SearchRequest.java, SearchResponse.javaと違うのは、グローバル要素として宣言する必要がないため、クラス宣言に@XmlRootElementアノテーションを付与していないことです。

package-info.java

入出力のXMLサンプルでは名前空間を宣言していましたが、前出の3つのJavaBeanには名前空間を示すアノテーションを付与していませんでした。名前空間は通常要素毎に分ける必要がない(実際入出力サンプルも同じ名前空間に属している)ため、個々のJavaBeanに同じことを記述するよりも、一括でまとめて書きたいところです。

package-info.javaはJava1.4までのpackage.htmlに相当するものです。このファイルに名前空間のアノテーションを記述することによって、同じパッケージに属するJavaBeanもその名前空間に属するとみなされます。

package-info.java

@javax.xml.bind.annotation.XmlSchema(
    namespace = "http://www.oki.com/hotelservice/schemas", 
    elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package bean;

通常のJavaファイルのようにパッケージを宣言し、そのパッケージ宣言に対してアノテーションを付与します。namespace属性で名前空間を記述し、elementFormDefault属性も上記のように記述します。XML Schemaにおいて、ローカル要素はデフォルト名前空間(XMLサンプルの例では"http://www.oki.com/hotelservice/schemas")に属しませんが、それを属させるようにするための記述です。名前空間に属する要素と属さない要素があると混乱を招くため、付けておくとよいでしょう。

SchemaGenTaskによるXML Schema生成

Java6では標準でschemagenコマンドが用意されており、そのコマンドを使うことによってJavaクラスからXML Schemaを生成できます。ただし生成されるXML Schemaの名前を指定できなかったり、余計なクラスが生成されてしまったりと、使い勝手があまり良くない為、以下のようなbuild.xmlを定義してXML Schemaを生成することにします。

build.xml

<path id="jxc.classpath">
    <fileset dir="${jaxb.home}/lib">
        <include name="*.jar" />
    </fileset>
    <pathelement path="${classes.dir}" />
</path>
    
<taskdef name="schemagen" classname="com.sun.tools.jxc.SchemaGenTask">
    <classpath refid="jxc.classpath" />
</taskdef>

<target name="schemagen">
    <schemagen classpathref="jxc.classpath" verbose="false">
        <src path="${src.dir}/bean" />
        <include name="*.java" />
        <schema namespace="http://www.oki.com/hotelservice/schemas" file="hotelService.xsd" />
    </schemagen>
</target>

schemagenタスクを実行すると、以下のXML Schemaが生成されます。

hotelService.xsd

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema elementFormDefault="qualified" version="1.0"
    targetNamespace="http://www.oki.com/hotelservice/schemas"
    xmlns:tns="http://www.oki.com/hotelservice/schemas"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xs:element name="searchRequest" type="tns:searchRequestType" />

    <xs:element name="searchResponse" type="tns:searchResponseType" />

    <xs:complexType name="hotelType">
        <xs:sequence>
            <xs:element name="name" type="xs:string" />
            <xs:element name="address" type="xs:string" />
            <xs:element name="emptyRoomNum" type="xs:int" />
        </xs:sequence>
    </xs:complexType>

    <xs:complexType name="searchRequestType">
        <xs:sequence>
            <xs:element name="startDate" type="xs:dateTime" />
            <xs:element name="endDate" type="xs:dateTime" />
        </xs:sequence>
    </xs:complexType>

    <xs:complexType name="searchResponseType">
        <xs:sequence>
            <xs:element name="hotel" type="tns:hotelType"
                maxOccurs="unbounded" minOccurs="0" />
        </xs:sequence>
    </xs:complexType>
</xs:schema>

ほぼ想定通りのXML Schemaが生成されました。ただし、どうしても制御できない部分もあります。例えばhotelTypeの型定義はsearchResponseTypeよりも後に来たほうが読みやすいですが、そういったことは恐らくできないのでしょう。繰り返しになりますが、この方法はContract-Firstではありません。入出力のXML仕様をきっちり定義する必要がある場合は、自動生成ではなくやはり自分でスキーマを定義する必要があるでしょう。

今回のまとめ

今回はSpring Web Services作成のための準備と、入出力XMLのXML Schemaを作成しました。次回はSOAPメッセージの送受信、Spring Beanとの連携、WSDLの公開など、Webサービスの実装方法を紹介します。

後編へ

Spring Web Services トップへ

資料室へ戻る


Copyright (c) 2007 OKI Software Co., Ltd.