Spring ve CXF ile SOAP Web Servisleri

Daha önce bir çok kere Web servisleri için blog yazısı yazdım. Örneğin JAX-WS ve Maven yazısında Metro kullanarak nasıl soap web servisleri yazılacağını, Axiz2 ile Web servisleri yazısında ise Axis 2 kullanarak nasıl soap web servisleri yazılacağını anlattım. Hatta bunlarla da yetinmeyim Jersey ile Rest web servislerinin nasıl yazılacağını bir yazı dizisi şeklinde anlattım. Fakat servislerle ilgili kütüphaneler ve frameworkler o kadar fazla ki ben her ne kadar hepsini anlatmaya çalışsam da eksik kısımlar kalıyor. Bu yazıda ise eksik kalan kısımları bir nebze daha azaltmak adına CXF ile SOAP (hatta isterseniz REST’te yazabilirsiniz) servislerinin nasıl yazılacağını anlatacağım.

Apache CXF

Apache CXF, spring ile kolay entegre olan, hem JAX-WS hem JAX-RS standartlarını destekleyen güzel bir web servis frameworku. CXF’e sadece kütüphane demeye dilim varmıyor. Çünkü loglama, güvenlik ve diğer utility’ler göz önüne alındığında tam bir framework çıkıyor karşımıza. Spring ile hemen hemen ek hiç bir şey yapmadan kolayca entegre olması hatta entegre halinde gelmesi ise işimizi çok kolaylaştırıyor.

Şimdi buırada bir ton tanımlama havada uçuşuyor, yok JAX-WS yok JAX-RS,  yok Metro yok Jersey falan filan. Kafa karışıklığını düzeltmek için bunları biraz açıklayalım.

Bildiğiniz üzere (yani en azından bildiğinizi varsayıyorum. Bilmiyorsanızda Google yardımıyla kolaylıkla öğrenebilirsiniz) iki temel servis mimarisi var. Bunlardan biri SOAP diğeri ise REST. Hemen hemen ikisi de tamamen standartlaşmış haldeler. Bu iki servis standartının java karşılığındaki standartları ise JAX-WS (SOAP, JSR-224) ve JAX-RS(REST, JSR-311). Bu arada açıklamaya çalışırken daha fazla tanımlama ekliyorum, JSR ise Java Specification Request, yani java’da bu da olsa dediğiniz şeylerin analizi. Java’nın geliştiricileri ise oturup bu JSR’ları java’ya ekliyorlar. Böylelikle bizde bunları kullanabiliyoruz.

Spring ve CXF

Şimdi burada önemli olan JAX-WS ve JAX-RS’in aslında sadece bir spesifikasyon olduğunu anlamak. Yani sadece API’lar. Neyin nasıl yapılacağını tanımlıyorlar. Bunun öncesinde her kütüphane kendisine has şeyler kullanıyordu. Örneğin JAX-WS öncesini ele alacak olursak, Axis doğrudan kendi tanımlama yöntemini kullanıyordu. Bir standart yoktu. Şimdi ise JAX-WS biliyorsanız  projenin hangi kütüphaneyi kullandığı sizin için önemli olmıuyor. Az öncede dediğim gibi JAX-WS ve JAX-RS sadece spesifikasyon. Bunların kullanılıp çalıştırılabilmesi için, bu API’ların implementasyonlarının yazılması gerekiyor. Bu spesifikasyonlarla eskiden SUN ilgilenirdi. Bundan dolayı ilk implementasyonlar da SUN’dan gelirdi. JAX-WS’i ilk uygulayanlar, referans implementasyonu olan Metro kütüphanesi iken, JAX-RS’i ilk uygulayanlar Jersey kütüphanesiydi. Şimdi SUN kalmasa da bu iki kütüphane Glassfish grubu tarafından destekleniyor. Tabi bu kütüphanelerin tek implementasyonunu SUN yazmıyor. Örneğin Axis2’de Jax-WS kütüphanesini implemente etti. Aynı şekilde CXF ise hem JAX-WS’i hem JAX-RS’i implemente etti. Bura da dikkat edilmesi gereken diğer bir kısım Metro ve Jersey sadece API’ı implemente ediyor ek bir özellik sunmuyor. Fakat öteki tarafntan Axis2 ve CXF ek özellikler de sunuyor. Sadece API’a bağlı kalmıyorlar. Örneğin CXF ek olarak çok rahat bir Spring entegrasyonu, güvenlik üzerine kütüphaneler, loglama vs.’de sağlıyor. Zaten önceki yazılarda (Camel ve XSLT, Camel ve SOAP)  benim CXF’i önerme sebebimde bunlardan dolayıydı.

Neyse, CXF’in ne olduğunu daha iyi anladığımıza göre şimdi teori kısmını biraz kenara bırakıp pratik kısmına geçebiliriz.

Hazırlık

Projeyi yazmaya başlamadan önce ne yazacağımızdan ve nasıl yazacağımızdan bahsedeceğim. Ben projeyi Ubuntu 14.04 altında, Oracle JDK 1.7 ile Eclipse Kepler kullanarak geliştiriyorum. Build aracı olarak her zaman ki Maven kullanıyoruz. CDI ortamı olarak Spring, web servis framework’ü olaraksa CXF kullanacağız. Projemiz basit bir HelloWorld servisi olacak. Servisimizde sayHelloTo diye bir metodumuz olacak. Bu metodumuz içerisine bir Person nesnesi alacak ve cevap olarak HelloWorldMessage üretecek. Mesaj üretme işlemini Spring üzerinde tanımlanmış bir MessageProvider bean’i sağlayacak. En sonda ise projemizi maven kullanarak gömülü jetty ile çalıştıracağız.

Maven ve Proje

Projeden kısaca bahsettiğimize göre Maven ile projeyi oluşturmaya başlayabiliriz. Maven ile, maven-archetype-webapp  archetpye’ını kullanarak projemizi oluşturuyoruz. Benim örneği yaparken kullandığım diğer bilgiler aşağıdaki gibidir.

  • Group Id: com.bahadirakin
  • Artifact Id:spring-cxf

Projemiz oluştuğuna göre, projemizin bağımlılılarını ekleyebilir ve nasıl build olacağını belirleyebiliriz. Bunun için spring-cxf klasörünün altındaki pom.xml dosyasını aşağıdaki şekilde değiştiriyoruz.

pom.xml

Tüm bağımlılıklarımızı eklemiş olduk. Şimdi ise src/main/webapp dosyasının altındaki web.xml dosyasının içeriğini aşağıdaki gibi değiştirelim.

Ayarların Yapılması

web.xml

Burada iki yeni tanımlama yapmış olduk. Hem CXF ile ilgili servlet’imizi belirledik. İşaretli olan 25. satırdan da görünlebileceği üzere, projemizin ana dizininden itibaren CXF ile ilgili servisler aranmaya başlayacak. Projenin ana dizinine gittiğimizde ise CXF tarafından oluşturulmuş servislerin (hem SOAP hem REST) listesini göreceğiz.

Diğer bir tanımlama ise Web projelerinde yapılan klasik spring tanımlaması. 14. satırda Spring’in ihtiyaç duyduğu Listener nesnesini tanımladık. 10. satırda ise projenin ana context dosyasını (applicationContext.xml) tanımladık. Bu context dosyasının nasıl ve nereye yaratılacağını şimdilik es geçiyorum. İleride Bean ve JAX-WS servis tanımlamalarımızı yaparken bu context dosyasını tanımlayacağız.

Servislerin Tanımlanması

Projeyi tanımlarken tek bir HelloWorld servisimizin olacağından bahsetmiştik. Şimdi ise bu servisi tanımlamaya başlayabiliriz. Bunun için src/main/java altında, com.bahadirakin.service isminde bir paket yaratıyoruz ve içerisine HelloWorldService isminde bir interface yaratıyoruz.

HelloWorldService.java

Biraz annotation’ların ne olduğundan bahsedelim.

  • @WebService: Bu sınıfın web servis olarak yayınlanacağını belirtiyor. name parametresi servis adının ne olacağını, targetNamespace ise hedef namespace adının ne olacağını belirtiyor.
  • @SOAPBinding:  Soap tanımlamalarını değişik şekillerde yapabiliyoruz. bize BARE şeklinde ki tanımlamayı tercih ettik. Bu sayede CXF bizim adımıza fazladan bir tanımlama yapmayacak. Eğer BARE değilde WRAPPED türünü seçseydik CXF bizim için ek Response ve Request nesneleri tanımlayacaktır.
  • @WebMethod: Hangi methodu dışarıdan erişime açmak istiyorsak onu işaretliyoruz. Bura daki operationName ve action name WSDL oluşturulurken kullanılıyor. Örneğin action WSDL oluşturulurken soapAction alanına yazılıyor.
  • @WebResult: Dönen nesne ile ilgili tanımlamalar yapıyoruz. Yine bu da WSDL generate edilirken kullanılıyor.
  • @WebParam: Parametreler ile ilgili tanımlamalarda kullanılıyor. Buradaki tanımlamalar da yine WSDL generate edilirken kullanılıyor.

Burada ki bazı tanımlamalar gereksiz ya da boş gibi gelebilir. Ama emin olun ki WSDL oluşumunda ve servis tanımlamalarınızda önemli bilgiler hepsi. Bu alanları değiştirip WSDL üzerinde ki değişikliklerin neler olacağına, Request ve Response’ların nasıl değiştiğine bakmanızı öneririm. Bunun için Soap-UI kullanabilirsiniz. Aynı şekilde client’larınızı generate ederekte ne şekilde değişikler yaptığına bakabilirisiniz.

Modellerin Tanımlanması

Böylelikle servis API’mızı tanımlamış olduk. Şimdi sıra bu API’ın implementasyonunu yazmaya geldi. Bunun için öncelikle model nesnelerimizi Person ve HelloWorldMessage nesnelerimizi tanımlıyoruz. Bunun için scr/main/java altına com.bahadirakin.model diye bir paket oluşturuyoruz ve içerisine Person ve HelloWorldMessage isminde sınıflar oluşturuyoruz.

[info_box]Person ve HelloWorldMessage nesnelerini kolayca oluşturmak için, HelloWorldService sınıfı içerisinde hata veren satıra gelip CTRL+1 kısa yolunu kullanabilirsiniz.[/info_box]

Person.java

 HelloWorldMessage.java

Böylelikle modellerimizi tamamlamış olduk. Modellerimizi tanımlarken XML’e dönüşümlerde JAXB kütüphanesini kullanıyoruz. Bu default olarak hemen hemen tüm JAX-WS kütüphanelerinde kullanılmaktadır. Burada dikkat etmeniz gereken kısımlar ise;

  • Model nesneleriniz JavaBean standartına uymalıdır. Getter/Setter’lar doğru tanımlanmış, Default Constructor tanımlanmış olmalıdır.
  • @XmlType: Bu sınıfın XML olarak Marshall edilebileceğini belirtir. name, xsd içerisinde hangi isimle görüleceğini belirtir. namespace ise bağlı bulunduğu xsd namespace’ini belirtir.
  • @XmlAccessorType: Bu XML olarak Marshal edilecek nesnenin özelliklerinin hangi şekilde alınacağını belirtir. Biz FIELD olarak belirledik. Bu sebepten XmlElement tanımlamalarını FIELD üzerinde yaptık. Eğer Property olarak belirtseydik, XmlElement tanımlamalarını get metodları üzerinde tanımlamamız gerekecekti.
  • @XmlElement: XML Marshalling sırasında, ilgili alanın ne şekilde tanımlanacağını belirtir. Nesenin zorunlu ya da nullanabilir olup olmadığı yine bu annotation üzerinden belirtilir.

Servis tanımlamalarında olduğu gibi, model tanımlamalarında da denemeler yapmanızı ve sonucun nasıl değiştiğini gözlemlemenizi öneririm.

Ana Servis Sınıfının Yazılması

Şimdi modellerimiz ve API’mız hazır. O halde ana servis sınıfımızı yazabiliriz. Bura da şunu belirtmek istiyorum, kodunuzu geliştirirken ana servis sınıfınızı arayüz sınıfınız gibi düşünmelisiniz. Yani nasıl hemen hemen hiç bir iş kuralını arayüzde tanımlamıyorsanız, aynı şekilde hiç bir iş kuralını da servis metodunuz içerisinde tanımlamamalısınız. Bizde bu şekilde ilerleyeceğimizden öncelikle iş kurallarının bulunduğu bir sınıf  yazacağız. Bu sınıfımızın adı MessageProducer olacak ve tek yaptığı gelen Person nesnesini bir iş mantığına göre HelloWorldMessage nesnesine çevirmek olacak. Örneğin eğer Person nesnesi null geliyorsa, “Hello to everyone” diye bir HelloWorldMessage sınıfı üretilirken, eğer Person nesnesi null gelmiyorsa “Hello, <surname>,<name>” şeklinde üretsin.

Bu iş mantığı için öncelikle MessageProvider interface’imizi src/main/java altında com.bahadirakin.producer içerisine tanımlıyoruz.

MessageProducer.java

Şimdi ise bunun implementasyonunu yazalım.

MessageProducerImpl.java

Burada da gördüğünüz üzere az önce belirttiğimiz iş tanımını kullandık. Böylesine basit bir örnekte iş tanımını ayrı bir sınıfa çıkmak saçma gibi gelebilir, fakat projeniz büyüdükçe aynı iş tanımına farklı yerlerde ihtiyacınız olacaktır. Bu da kodunuzun tekrar kullanabilirliğini arttıracaktır. Bu projede ise benim kullanma sebebim hem güzel bir örnek yapmak hem de spring kısmında inject edilebilecek bir servis yaratmak.

İş mantığımızın bulunduğu katmanı gerçeklediğimize göre şimdi asıl web servisimizi yazmaya başlayabiliriz. İş mantığımızı yazdığımız için bunu yazmak hemen hemen hiç zaman almayacaktır.

HelloWorldServiceImpl.java

Ana servis sınıfımız bu kadar basit. Şimdi bura da dikkat etmenizi istediğim bazı ayrıntılar var.

  • Servis sınıfımızın bir Default Constructor‘ı yok. Bu demek oluyor ki bu sınıfın yaratılabilmesinin tek yolu, MessageProducer sınıfı yani iş mantığı sağlamak. Inversion of Control deyimiyle Constructor Injection yapılması gerekiyor. Bunu şimdilik dert etmiyoruz. Bunu spring bizim adımıza yapacak. Hani başta oluşturmayı atladığımız bir applicationContext.xml vardı hani web.xml içerisinde tanımlamıştık. İşte applicationContext.xml içerisinde Constructor injection tanımlaması yapacağız.
  • Web servisin API’ında (HelloWorldService.java) tanımladığımız hiç bir şeyi burada tanımlamamıza gerek yok.

Böylelikle java tarında yapmamız gereken herşeyi yaptık. Şimdi spring tarafına geçip gerekli bağlamaları, injection tanımlamalarını yapabiliriz.

Spring Tanımlamaları

Spring ile ilgili ayarlarımızı yazımızın başında, maven ile projemizi oluştururken yapmıştık. web.xml içerisinde 10. satırda applicationContext.xml dosyasını kullanarak projeyi ayağa kaldıracağımızı belirtmiştik. Fakat daha applicationContext.xml dosyasını oluşturmadık. applicationContext.xml dosyamızı src/main/resources klasörünün altına oluşturuyoruz. İçeriğini ise aşağıdaki gibi yapıyoruz.

applicationContext.xml

Burada dikkat etmeniz gerekenler ise;

  • 9. ve 10. satırdaki import işlemlerini CXF için yapmamız gerekiyor. Bu xml dosyaları normal Spring bean tanımlamaları. Eğer CXF ile ilgiliyseniz, bu dosyaları açıp okumanızı ve context’e nelerin eklendiğine bakmanızı tavsiye ederim.
  • 16. satırda, Ana servis sınıfını yazarken söylediğimiz gibi Constructor Injection yapıyoruz. Eğer ne zaman Constructor Injection ne zaman Setter Injection yapacağınıza karar veremiyorsanız, şöyle basit bir kural izleyebilirsiniz. Böylelikle çoğu durumda ek bir düşünme yapmadan doğru kararı vermiş olursunuz. Eğer yaratılacak sınıf, inject edilecek sınıf olmadan düzgün çalışabiliyorsa Setter Injection, eğer düzgün çalışamıyorsa Constructor Injection yapınız. Eğer %100 Consturctor injection çıkıyorsa, mimarinizde bir hata olabilir ya da CDI mekanizmasını yanlış kullanıyor olabilirsiniz. Martin Fowler’ın bu yazısını okumanızı tavsiye ederim.
  • 19. satırda başlayan JAX-WS tagi ile de servis tanımlamamızı yapmış oluyoruz. Eklediğimiz Interceptor‘lar loglama için. Tanımladığımız endpoint’e gelen ve giden talepleri ve cevapları loglamaktadırlar.

Projenin Çalıştırılması

Projemizin tüm ayarlarını ve tanımlamalarını yaptığımıza göre projemizi çalıştırabiliriz. Ben maven kullanarak gömülü jetty üzerinden çalıştıracağım ama siz isterseniz Tomcat ya da başka bir Servlet container üzerinde de çalıştırabilirsiniz. Bunun için proıjenin ana dizinine gelip “mvn clean install jetty:run” komutunu çalıştırıyorum. Herşeyin başarılı bir şekilde build edildiğini ve çalıştığını gördüktek sonra “http://localhost:8080” adresine yöneldiğiniz de aşağıdakine benzer bir ekranla karşılaşmanız gerekiyor.

Burada gördüpünüz gibi REST servisleri ile ilgili kısım boş. Eğer rest servisi de tanımlamış olsaydık o servisler de burada listelenecekti. Yine aynı şekilde WSDL linkine tıkladığınızda ilgili WSDL dosyasının açılması gerekiyor. Bu WSDL dosyasını kullanarak SOAP-UI’da proje oluşturup servislerinizi test edebilirsiniz.

Son durumda projenin yapısı ise aşağıdaki gibi oluyor.

Son

Böylelikle projemizin sonuna geldik. Projenin tamamına aşağıdaki adresten erişebilirsiniz.

End Of Line

  • Celal

    Bahadir kardeş seni fark etmek çok güzel. Yazilarin cok güzel. ve gercekten cok menun kaliyorum bu yazilarindan…detayli, jargonlari tek tek aciklamak….cok hoş 🙂
    teşekkürler…