Sansür, bir toplumun kendine olan güvensizliğini yansıtır ve otoriter rejimlerin belirgin bir özelliğidir.

--Potter Stewart

16.06.2008

Spring Web Servis ve Anlaşma-Önce (Contract-First) Yaklaşımı ile Web Servis geliştirme

Web Servisleri bir makinede sunulan hizmetin platform bağımsız olarak başka istemci makinelerce kullanılabilirliğini sağlamaktadır. Web Servis geliştiriminde temelde iki yaklaşım izlenmektedir: Anlaşma-önce(Contract-first) ve Anlaşma-sonra(Contract-last). Anlaşma-önce yaklaşımında servis ve servisten istemde bulunacak istemcilerin anlaşabilmeleri için (sunucu hangi servisleri sağlıyor, bu servislerin girdi ve çıktı tipleri neler, vs.) XML tabanlı bir belge (WSDL – Web Service Definition Language - http://www.w3.org/TR/wsdl) baz alınır. Anlaşma-sonra yaklaşımda ise yazılan kodlar aracılığıyla arka planda WSDL belgesi üretilerek geliştirim ve haberleşme karmaşıklığı geliştiriciden soyutlanmaktadır.

Anlaşma-sonra yaklaşım ile web servis gerçekleştiriminde Apache – CXF ön plana çıkarken Anlaşma-önce yaklaşımda Spring Web Service geliştirimi kolaylaştırmaktadır.

Her ne kadar Anlaşma-sonra yaklaşım ilk etapta geliştirim kolaylığı açısından daha tercih edilebilir görünmekteyse de sunucu kodu değişince, kodtan otomatik üretilen WSDL belgesinin de değişmesi istemci tarafını da değişikliğe zorlar. Bu ve diğer sebepler yüzünden büyük sistemlerde Anlaşma-önce yaklaşım tercih edilmektedir.

Spring Web Service(SWS) bir adım daha ileri giderek anlaşma belgesiyle geliştirim arasındaki bağımlılığın azaltılmasını önermektedir. Gelen XML mesajlarının namespace ve kök imini (tag) kullanarak mesajı, mesajla ilgilenen sınıfa (endpoint sınıfları) yönlendirmektedir. Bunun için de istemci/sunucu arasında mesaj iletimi için gönderilen XML mesajlarının tanımlarını (XSD – XML Schema Definition) kullanmaktadır.

SWS ile iş mantığını barındıran servislerimizi Web servis olarak kullanıma açmak için gereken ilk adımı sunucuya gelen web servis istemlerini ele alacak Servlet’i web.xml’e tanımlayarak başlayalım:

<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>/springws/*</url-pattern>

</servlet-mapping>

MessageDispatcherServlet ile sunucuya gelen /springws/* istemleri mesaj-endpoint eşlemesine tabi tutulacaktır. Eşleme, konfigürasyon kütüğünde ya da annotation kullanılarak yapılabilir. Servlet ismi olarak MessageDispatcherServlet’e verdiğimiz isim /WEB-INF dizini altında yer alacak konfigürasyon kütüğünün ismini de belirler: servlet-ws-servlet.xml

Konfigürasyon kütüğü kullanılarak yapılan eşleme ve sınıf yapısı şu şekilde olur:

<!-- End Point Example with Marshalling -->

<bean id="marshallingExampleServiceDoSomeJobEndpoint" class="tr.com.dasgin.MarshallingExampleServiceDoSomeJobEndPoint">

<constructor-arg ref="exampleService"/>

<constructor-arg ref="xmlBeansMarshaller"/>

</bean>

<!-- Marshaller -->

<bean id="xmlBeansMarshaller" class="org.springframework.oxm.xmlbeans.XmlBeansMarshaller"/>

<!-- Mapping -->

<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">

<property name="mappings">

<props>

<prop

key="{http://www.dasgin.com.tr/sws/schemas}DoSomeJobRequest">

marshallingExampleServiceDoDomeJobEndpoint

</prop>

</props>

</property>

</bean>

public class MarshallingExampleServiceDoSomeJobEndPoint extends AbstractMarshallingPayloadEndpoint{

private final ExampleService exampleService;

public MarshallingExampleServiceDoSomeJobEndPoint(ExampleService exampleService, Marshaller marshaller){

super(marshaller);

this.exampleService = exampleService;

}

protected Object invokeInternal(Object request) throws Exception {

...

exampleService.doSomeJob( ... );

...

}

}

Burada, önceden tanımlı ve iş mantığını yürüten ExampleService sınıfı, bir metoduna vekillik yapan EndPoint sınıfına bağlanmıştır. Burada dikkat edilmesi gereken husus web servis olarak sunulan her bir metot için ayrı bir EndPoint sınıfı yazılmasıdır. Annotation ile yapılan tanımlamalarda bu methodlar tekbir EndPoint sınıfında toplanabilir.

EndPoint sınıfına ayrıca istem olarak gelen ya da cevap olarak dönülen XML mesajlarına marshalling/unmarshalling uygulayacak olan XmlBeansMarshaller sınıfı da bağlanmıştır. Bu sayede XML <-> Nesne dönüşümleri otomatik olarak gerçekleştirilecektir. (XMLBeans kütüphanesi ile XSD ya da WSDL belgesinden Java kodunda kullanılacak Java Sınıfları üretilmiştir.)

Eşleme işlemini yapan bean tanımı incelendiğinde tanımlanan EndPoint beanler’in bir anahtar aracılığıyla eşlendiği görülür. Bu anahtar tanımı ile sunucuya gelen web servis istemlerinde yer alan namespace’i http://www.dasgin.com.tr/sws/schemas olan ve kök imi DoSomeJobRequest olan XML mesajlarının marshallingExampleServiceDoDomeJobEndpoint Endpoint sınıfına yönlendirilmeleri sağlanmaktadır.

<DoSomeJobRequest xmlns="http://www.dasgin.com.tr/sws/schemas">

<foo>SOME</foo>

<foo>JOB!</foo>

</DoSomeJobRequest>

Sunucuya gelen ve endpoint’e yönlendirilecek olan yukarıdaki örnek XML mesajının XSD şeması da şu şekildedir:

<xsd:schema xmlns:sch="http://www.dasgin.com.tr/sws/schemas"

xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"

targetNamespace="http://www.dasgin.com.tr/sws/schemas">

<xsd:element name="DoSomeJob">

<xsd:complexType>

<xsd:sequence>

<xsd:element maxOccurs="unbounded" minOccurs="0" type="sch:FooType"/>

</xsd:sequence>

</xsd:complexType>

</xsd:element>

<xsd:simpleType name="FooType">

<xsd:restriction base="xsd:string">

<xsd:pattern value="[A-Z]+"/>

</xsd:restriction>

</xsd:simpleType>

</xsd:schema>

Annotation kullanılarak yapılan eşlemelerde en önemli fark, yöntemlerin tek bir EndPoint sınıfında toplanabilme olanağıdır:

<!-- End Point Example with Marshalling and Annotation (MultipleMethodEndPoint) -->

<bean id="annotationExampleServiceEndPoint" class="tr.com.dasgin.AnnotationExampleServiceEndPoint">

<property name="exampleService" ref="exampleService"/>

</bean>

<bean class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">

<constructor-arg ref="xmlBeansMarshaller"/>

</bean>

<!--

Endpoint sinifinda @PayloadRoot annotation i kullanilacak olursa asagidaki bean yaratilmali. @PayloadRoot annotation i ile

gelen mesajlarin(xml) namespace ve localpart larina gore ayiriyor.

-->

<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping"/>

<bean id="xmlBeansMarshaller" class="org.springframework.oxm.xmlbeans.XmlBeansMarshaller"/>

@Endpoint

public class AnnotationExampleServiceEndPoint {

private ExampleService exampleService;

public ExampleService getExampleService() {

return exampleService;

}

public void setExampleService(ExampleService exampleService) {

this.exampleService = exampleService;

}

@PayloadRoot(localPart="DoSomeJob", namespace="http://www.dasgin.com.tr/sws/schemas")

public XMLObject doSomeJob(XMLObject request) throws Exception {

...

exampleService.doSomeJob( ... )

...

}

}

Web Service İstemci Tarafı

Bir istemci ile hizmet sunan web servise bağlanabilmek için SWS gerekli desteği de sunmaktadır. Spring bean olarak tanımlanacak WebServiceTemplate sınıfı ile istemci tarafını yönetmek mümkündür:

<bean id="messageFactory"

class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />

<bean id="xmlBeansMarshaller" class="org.springframework.oxm.xmlbeans.XmlBeansMarshaller" />

<bean id="webServiceTemplate"

class="org.springframework.ws.client.core.WebServiceTemplate">

<constructor-arg ref="messageFactory" />

<property name="marshaller" ref="xmlBeansMarshaller" />

<property name="unmarshaller" ref="xmlBeansMarshaller" />

<property name="messageSender">

<bean class="org.springframework.ws.transport.http.CommonsHttpMessageSender">

<!-- 0 means unlimited -->

<property name="readTimeout" value="0" />

</bean>

</property>

<property name="defaultUri"

value="http://localhost:8080/sunucu.webservices/springws" />

</bean>

Yukarıda tanımlanan webServiceTemplate bean’i kullanılarak defaultUri ile tanımlanan adreste hizmet veren web servise erişim sağlanabilir. Ayrıca webServiceTemplate bean’ine gönderilecek ve alınacak XML mesajlarına marshalling/unmarshalling uygulayacak mekanizma da (xmlBeansMarshaller) bağlanmıştır. Ayrıca servisten dönecek cevap için timeout değeri de limitsiz olarak ayarlanmıştır. Bu örneğe göre XMLObject nesnesi

XMLObject response = webServiceTemplate.marshalSendAndReceive( XMLObject )

Deyimi ile önce marshalling işlemine tabi tutulduktan sonra XML mesajı olarak belirlenen adrese istem olarak gönderilecek ve dönen XML cevap mesajı unmarshalling işleminin ardından response nesnesi ile ele alınacaktır.( XMLBeans kütüphanesi ile XSD ya da WSDL belgesinden Java kodunda kullanılacak Java Sınıfları üretilmiştir.)

SWS ile web servis geliştirme konusunda daha ayrıntılı bilgi için (otomatik wsdl üretme, interceptors, validators, vs.) referans belgesine başvurulabilir.