Spring Web Services まとめ その2
Webサービスの実装 (Chapter 3)
contract-firstなWebサービス開発を行う際は、実際に送受信されるXMLメッセージ構造を意識することが最も重要。
XMLメッセージ構造
ここでは、休暇申請情報を表す以下のXMLメッセージ*1を例とする。
<HolidayRequest xmlns="http://mycompany.com/hr/schemas"> <Holiday> <StartDate>2006-07-03</StartDate> <EndDate>2006-07-07</EndDate> </Holiday> <Employee> <Number>42</Number> <FirstName>Arjen</FirstName> <LastName>Poutsma</LastName> </Employee> </HolidayRequest>
XMLデータ定義
ここでは、XML Schemaを利用してXMLデータを定義する。以下は、上記の休暇申請情報を定義したXML Schemaである。
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:hr="http://mycompany.com/hr/schemas" elementFormDefault="qualified" targetNamespace="http://mycompany.com/hr/schemas"> <xs:element name="HolidayRequest"> <xs:complexType> <xs:all> <xs:element name="Holiday" type="hr:HolidayType"/> <xs:element name="Employee" type="hr:EmployeeType"/> </xs:all> </xs:complexType> </xs:element> <xs:complexType name="HolidayType"> <xs:sequence> <xs:element name="StartDate" type="xs:date"/> <xs:element name="EndDate" type="xs:date"/> </xs:sequence> </xs:complexType> <xs:complexType name="EmployeeType"> <xs:sequence> <xs:element name="Number" type="xs:integer"/> <xs:element name="FirstName" type="xs:string"/> <xs:element name="LastName" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:schema>
サービス定義(WSDL)
Spring-WSでは、WSDLの作成は必須ではない。XML Schemaの定義といくつかのコンフィギュレーションにより、Spring-WSが代わりにWSDLを公開してくれるからである。
ここでは、手作業でWSDLを作成した場合の記述例を示す。
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:schema="http://mycompany.com/hr/schemas" xmlns:tns="http://mycompany.com/hr/definitions" targetNamespace="http://mycompany.com/hr/definitions"> <wsdl:types> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:import namespace="http://mycompany.com/hr/schemas" schemaLocation="hr.xsd"/> </xsd:schema> </wsdl:types> <wsdl:message name="HolidayRequest"> <wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/> </wsdl:message> <wsdl:portType name="HumanResource"> <wsdl:operation name="Holiday"> <wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="HumanResourceBinding" type="tns:HumanResource"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="Holiday"> <soap:operation soapAction="http://mycompany.com/RequestHoliday"/> <wsdl:input name="HolidayRequest"> <soap:body use="literal"/> </wsdl:input> </wsdl:operation> </wsdl:binding> <wsdl:service name="HumanResourceService"> <wsdl:port binding="tns:HumanResourceBinding" name="HumanResourcePort"> <soap:address location="http://localhost:8080/holidayService/"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
プロジェクトの作成
ここでは、Maven2*2を利用したプロジェクトを作成する。
mvn archetype:create -DarchetypeGroupId=org.springframework.ws -DarchetypeArtifactId=spring-ws-archetype -DarchetypeVersion=1.0-rc2 -DgroupId=com.mycompany.hr -DartifactId=holidayService
上記のコマンドにより、holidayServiceディレクトリが作成される。
続けて"/src/main/webapp"ディレクトリ配下の"WEB-INF/web.xml"を以下の内容に編集する。
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <display-name>MyCompany HR Holiday Service</display-name> <!-- take especial notice of the name of this servlet --> <servlet> <servlet-name>spring-ws</servlet-name> <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>spring-ws</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
また、"WEB-INF/spring-ws-servlet.xml"ファイル*3も合わせて作成しておく。このファイルには、Spring-WSで利用するBeanを定義していく。
エンドポイントの実装
Spring-WSでは、受け取ったXMLメッセージをハンドリングするため、以下のいずれかのエンドポイントを実装する必要がある。
一般的には、SOAPメッセージではなくpayloadのみハンドリングしたいため、payload endpointsを実装する。
以下は、JDomを利用した payload endpointsの実装例である*4。JDomを利用したpayload endpointsを実装する場合は、AbstractJDomPayloadEndpointを継承したクラスを実装する。
package com.mycompany.hr.ws; import java.text.SimpleDateFormat; import java.util.Date; import com.mycompany.hr.service.HumanResourceService; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.Namespace; import org.jdom.xpath.XPath; import org.springframework.ws.server.endpoint.AbstractJDomPayloadEndpoint; public class HolidayEndpoint extends AbstractJDomPayloadEndpoint { private XPath startDateExpression; private XPath endDateExpression; private XPath nameExpression; private final HumanResourceService humanResourceService; public HolidayEndpoint(HumanResourceService humanResourceService) { this.humanResourceService = humanResourceService; Namespace namespace = Namespace.getNamespace("hr", "http://mycompany.com/hr/schemas"); startDateExpression = XPath.newInstance("//hr:StartDate"); startDateExpression.addNamespace(namespace); endDateExpression = XPath.newInstance("//hr:EndDate"); endDateExpression.addNamespace(namespace); nameExpression = XPath.newInstance("concat(//hr:FirstName,' ',//hr:LastName)"); nameExpression.addNamespace(namespace); } protected Element invokeInternal(Element holidayRequest) throws Exception { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date startDate = dateFormat.parse(startDateExpression.valueOf(holidayRequest)); Date endDate = dateFormat.parse(endDateExpression.valueOf(holidayRequest)); String name = nameExpression.valueOf(holidayRequest); humanResourceService.bookHoliday(startDate, endDate, name); return null; } }
コンストラクタでHumanResourceServiceを受け取っているが、これはSpring Frameworkのコンストラクタ・インジェクションを利用して実装クラスの参照を渡すことができる。
invokeInternal()はテンプレートメソッドであり、引数のelementには
また、invokerInternal()の戻り値はElementとなっているが、サービスのレスポンスが存在する場合は、XMLメッセージ作成して返す必要がある。ここでは、サービスのレスポンスはないため、nullを返している。
以下は、上記の実装を行うためのMaven2のpom.xmlファイルの例である。
<dependencies> <dependency> <groupId>org.springframework.ws</groupId> <artifactId>spring-ws-core</artifactId> <version>1.0-rc2</version> </dependency> <dependency> <groupId>jdom</groupId> <artifactId>jdom</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>javax.xml.soap</groupId> <artifactId>saaj-api</artifactId> <version>1.3</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.sun.xml.messaging.saaj</groupId> <artifactId>saaj-impl</artifactId> <version>1.3</version> <scope>runtime</scope> </dependency> <dependencies>
最後に、作成したpayload endpoints実装をSpring-WSの設定ファイル(spring-ws-servlet.xml)に記述する。
<beans xmlns="http://www.springframework.org/schema/beans"> <bean id="holidayEndpoint" class="com.mycompany.hr.ws.HolidayEndpoint"> <constructor-arg ref="hrService"/> </bean> <bean id="hrService" class="com.mycompany.hr.service.StubHumanResourceService"/> </beans>
また、メッセージのルーティング設定を行うため、以下の記述も追記する。
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping"> <property name="mappings"> <props> <prop key="{http://mycompany.com/hr/schemas}HolidayRequest">holidayEndpoint</prop> </props> </property> <property name="interceptors"> <bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/> </property> </bean>
この設定により、"http://mycompany.com/hr/schemas"名前空間の
WSDLを公開するために、さらに以下の記述も追記する*5。
<bean id="holiday" class="org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition"> <property name="builder"> <bean class="org.springframework.ws.wsdl.wsdl11.builder.XsdBasedSoap11Wsdl4jDefinitionBuilder"> <property name="schema" value="/WEB-INF/hr.xsd"/> <property name="portTypeName" value="HumanResource"/> <property name="locationUri" value="http://localhost:8080/holidayService/"/> <property name="targetNamespace" value="http://mycompany.com/hr/definitions"/> </bean> </property> </bean> </beans>
以上より、http://localhost:8080/holidayService/holiday.wsdlにアクセスすることでWSDLが取得可能となる。また、http://localhost:8080/holidayService/でサービスを実行できる*6。
作成したプロジェクトは、mvn installを実行してWARファイルを作成し、TomcatなどのAPサーバにデプロイすることで実行可能となる。