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.
Åž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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.bahadirakin</groupId> <artifactId>spring-cxf</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>spring-cxf</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.java.version>1.6</project.java.version> <!-- Versions --> <slf4j.version>1.7.5</slf4j.version> <spring.version>4.0.5.RELEASE</spring.version> <cxf.version>3.0.0</cxf.version> </properties> <build> <finalName>spring-cxf</finalName> <plugins> <!-- Compiler --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>${project.java.version}</source> <target>${project.java.version}</target> </configuration> </plugin> <!-- Eclipse Plugin --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-eclipse-plugin</artifactId> <version>2.9</version> <configuration> <downloadJavadocs>true</downloadJavadocs> <downloadSources>true</downloadSources> <additionalConfig> <file> <name>.settings/org.eclipse.core.resources.prefs</name> <content><![CDATA[eclipse.preferences.version=1${line.separator}encoding/<project>=${project.build.sourceEncoding}${line.separator}]]></content> </file> </additionalConfig> </configuration> </plugin> <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>9.2.1.v20140609</version> </plugin> </plugins> </build> <dependencies> <!-- LOGGING DEPENDENCIES --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-ext</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.0.13</version> </dependency> <!-- Spring Dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <!-- CXF Dependencies --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>{cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${cxf.version}</version> </dependency> <!-- Other dependencies --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <!-- Test Engine Dependencies --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> </project> |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="spring-cxf" version="2.5"> <display-name>spring-cxf</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>CXFServlet</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app> |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package com.bahadirakin.service; import java.io.Serializable; import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; import javax.jws.soap.SOAPBinding.ParameterStyle; import com.bahadirakin.model.HelloWorldMessage; import com.bahadirakin.model.Person; @WebService(name="HelloWorldService", targetNamespace="http://service.bahadirakin.com") @SOAPBinding(parameterStyle=ParameterStyle.BARE) public interface HelloWorldService extends Serializable{ @WebMethod(operationName="sayHelloWorldTo", action="sayHelloWorldTo") @WebResult(name="helloWorldMessage", partName="helloWorldMessage", targetNamespace="http://model.bahadirakin.com") HelloWorldMessage sayHelloWorldTo(@WebParam(partName = "person", name = "person", targetNamespace = "http://model.bahadirakin.com/" ) Person person); } |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | package com.bahadirakin.model; import java.io.Serializable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; @XmlType(name = "person", namespace = "http://model.bahadirakin.com", propOrder = {"name", "surname"}) @XmlAccessorType(XmlAccessType.FIELD) public class Person implements Serializable{ private static final long serialVersionUID = 1L; @XmlElement(name="name", namespace="http://model.bahadirakin.com" , nillable=false, required=true, type=String.class) private String name; @XmlElement(name="surname", namespace="http://model.bahadirakin.com" , nillable=false, required=true, type=String.class) private String surname; public Person() { super(); } public Person(String name, String surname) { super(); this.name = name; this.surname = surname; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSurname() { return surname; } public void setSurname(String surname) { this.surname = surname; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((surname == null) ? 0 : surname.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (surname == null) { if (other.surname != null) return false; } else if (!surname.equals(other.surname)) return false; return true; } @Override public String toString() { return "Person [name=" + name + ", surname=" + surname + "]"; } } |
 HelloWorldMessage.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | package com.bahadirakin.model; import java.io.Serializable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; @XmlType(name = "helloWorldMessage", namespace = "http://model.bahadirakin.com") @XmlAccessorType(XmlAccessType.FIELD) public class HelloWorldMessage implements Serializable { private static final long serialVersionUID = 1L; @XmlElement(name = "message", namespace = "http://model.bahadirakin.com", nillable = false, required = true, type = String.class) private String message; public HelloWorldMessage() { super(); } public HelloWorldMessage(String message) { super(); this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((message == null) ? 0 : message.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; HelloWorldMessage other = (HelloWorldMessage) obj; if (message == null) { if (other.message != null) return false; } else if (!message.equals(other.message)) return false; return true; } @Override public String toString() { return "HelloWorldMessage [message=" + message + "]"; } } |
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
1 2 3 4 5 6 7 8 9 10 11 | package com.bahadirakin.producer; import java.io.Serializable; import com.bahadirakin.model.HelloWorldMessage; import com.bahadirakin.model.Person; public interface MessageProducer extends Serializable{ HelloWorldMessage produceHelloWorldMessage(final Person person); } |
Şimdi ise bunun implementasyonunu yazalım.
MessageProducerImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | package com.bahadirakin.producer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.bahadirakin.model.HelloWorldMessage; import com.bahadirakin.model.Person; public class MessageProducerImpl implements MessageProducer { private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory .getLogger(MessageProducerImpl.class); private static final String DEFAULT_HELLOWORLD_MESSAGE_FORMAT = "Hello to %s,%s"; @Override public HelloWorldMessage produceHelloWorldMessage(Person person) { if (person == null) { logger.info("Person is null"); return new HelloWorldMessage("Hello to everyone"); } logger.info("Saying hello to person: {}", person); return new HelloWorldMessage(String.format( DEFAULT_HELLOWORLD_MESSAGE_FORMAT, person.getSurname(), person.getName())); } } |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package com.bahadirakin.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.bahadirakin.model.HelloWorldMessage; import com.bahadirakin.model.Person; import com.bahadirakin.producer.MessageProducer; public class HelloWorldServiceImpl implements HelloWorldService { private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory .getLogger(HelloWorldMessage.class); private MessageProducer messageProducer; public HelloWorldServiceImpl(MessageProducer messageProducer) { super(); logger.info("Hello world service is created"); this.messageProducer = messageProducer; } @Override public HelloWorldMessage sayHelloWorldTo(Person person) { logger.info("Saying hello world to {}", person); return messageProducer.produceHelloWorldMessage(person); } } |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"> <!-- needed cxf imports --> <import resource="classpath:META-INF/cxf/cxf.xml" /> <import resource="classpath:META-INF/cxf/cxf-servlet.xml" /> <bean id="messageProducer" class="com.bahadirakin.producer.MessageProducerImpl" scope="singleton" /> <bean id="helloWorldService" class="com.bahadirakin.service.HelloWorldServiceImpl"> <constructor-arg ref="messageProducer" /> </bean> <jaxws:endpoint id="helloWorldServiceEndpoint" implementor="#helloWorldService" address="/HelloWorldService"> <jaxws:properties> <entry key="schema-validation-enabled" value="true" /> </jaxws:properties> <jaxws:inInterceptors> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor" /> </jaxws:inInterceptors> <jaxws:outInterceptors> <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" /> </jaxws:outInterceptors> </jaxws:endpoint> </beans> |
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