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

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.

screenshotBurada 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.

project-spring-cxfSon

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

End Of Line