Usługi sieciowe – szybki efekt dzięki CXF
Mój nauczyciel matematyki zwykł mówić, że “dobry matematyk to leniwy matematyk”. Zwykł tak komentować karkołomne metody obliczeń stosowane w sytuacjach kiedy można coś zrobić prościej. Lubię tę zasadę stosować również w odniesieniu do programowania.
Kiedy przyszło mi niedawno stworzyć usługi sieciowe (ang. Web Services) w Javie pomyślałem o Apache CXF. CXF jest szkieletem (ang. framework) pomagającym budować usługi korzystające z API takich jak JAX-WS i zdolne do komunikacji używając protokołów SOAP, REST czy HTTP.
Kontrakt
Przy tworzeniu usług sieciowych można stosować dwa podejścia:
- WSDL first – najpierw tworzymy kontrakt, jakim jest plik WSDL opisujący nasze usługi a następnie przechodzimy do implementacji;
- Implementation first – najpierw implementujemy logikę a później wystawiamy ją w formie usług sieciowych i generujemy WSDL;
Ja osobiście lubię zabrać się do pracy kiedy wiem co mam zrobić, ot takie dziwactwo. Stąd zakładam sytuację kiedy najpierw definiujemy kontrakt jaki realizowała będzie moja usługa sieciowa.
Dla zaprezentowania CXF utworzę bardzo prostą usługę sieciową, która będzie odpowiadała na zadane pytanie. Zakładam otrzymać ciąg znaków i ciąg znaków odeślę w odpowiedzi (taka pogawędka).
Kontrakt WSDL mógłby wyglądać tak (talk.wsdl):
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://michalmech.pl/service/talk/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://michalmech.pl/service/talk/" name="talk"> <wsdl:message name="Ask"> <wsdl:part name="question" type="xsd:string" /> </wsdl:message> <wsdl:message name="AskResponse"> <wsdl:part name="answer" type="xsd:string" /> </wsdl:message> <wsdl:portType name="talk"> <wsdl:operation name="Ask"> <wsdl:input message="tns:Ask" /> <wsdl:output message="tns:AskResponse" /> </wsdl:operation> </wsdl:portType> <wsdl:binding name="talkSOAP" type="tns:talk"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="Ask"> <soap:operation soapAction="http://michalmech.pl/service/talk/ask" /> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="TalkOperations"> <wsdl:port binding="tns:talkSOAP" name="talkSOAP"> <soap:address location="http://michalmech.pl" /> </wsdl:port> </wsdl:service> </wsdl:definitions>
Aplikacja
Teraz przyda się Maven, którym utworzymy szybko projekt aplikacji internetowej i zajmiemy się zależnościami. Sam Maven nie jest przedmiotem tego wpisu więc wspomnę tylko, że zaczynamy od polecenia:
mvn archetype:generate
Po utworzeniu projektu plik talk.wsdl umieszczamy w zasobach naszego projektu: src/main/resources/
Oto kompletny plik POM, który zawiera zależności oraz informacje o budowaniu projektu:
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.michalmech.cxfdemo</groupId>
<artifactId>wsdl-first</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>CXF Demo - WSDL-first</name>
<dependencies>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-core</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-common-utilities</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.0.0.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>2.1.4</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${basedir}/src/main/java</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>src/main/resources/talk.wsdl</wsdl>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.22</version>
</plugin>
</plugins>
</build>
</project>
Widzimy zależności od pakietów CXF oraz pluginu CXF Codegen, który odwali za nas trochę roboty. Spring Framework dostarczy nam swój kontener IoC a serwer Jetty przyda się do testowania aplikacji.
No dobrze czas na magię. Wspominałem o leniach no to wypadałoby teraz osiągnąć coś niewielkim nakładem pracy, co niniejszym czynimy wykonując polecenia:
mvn generate-sources mvn eclipse:clean eclipse:eclipse
Pierwsze polecenie wygeneruje nam kod języka Java na podstawie kontraktu WSDL. Drugie utworzy pliki konfiguracyjne potrzebne do importu projektu do mojego ulubionego IDE. Na obecnym etapie projekt powinien wyglądać tak:
Zajmijmy się teraz implementacją naszej usługi. Nie będzie zbyt wyszukana:
package pl.michalmech.service;
import pl.michalmech.service.talk.Talk;
public class TalkImpl implements Talk {
public String ask(String question) {
return "I don't know answer for \"" + question + "\" question.";
}
}
Aby nasza usługa sieciowa działała przyda nam się zdefiniowanie kontekstu aplikacji (applicationContext.xml) oraz deskryptora rozmieszczenia (web.xml):
src/main/webapp/WEB-INF/applicationContext.xml
<?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:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:cxf="http://cxf.apache.org/core" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"> <import resource="classpath:META-INF/cxf/cxf.xml" /> <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" /> <import resource="classpath:META-INF/cxf/cxf-servlet.xml" /> <jaxws:endpoint implementor="pl.michalmech.service.TalkImpl" address="/service/talk" /> </beans>
src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app id="WebApp_9" version="2.4" 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"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/*Context.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
I znów nie musimy martwić się o servlet ponieważ o wszystko zadba za nas CXF.
Uruchomienie
Teraz przyda nam się Jetty. Uruchamiamy aplikację poleceniem:
mvn jetty:run
oraz sprawdzamy rezultaty w przeglądarce. Wyniki działania aplikacji powinny być następujące:
http://localhost:8080/wsdl-first
![]()
http://localhost:8080/wsdl-first/service/talk?wsdl
To wszystko co potrzebne było do uruchomienia prostej usługi sieciowej. Przykład jest trywialny i dotyka ledwie czubka góry lodowej zarówno możliwości CXF jak i zastosowań zamych usług sieciowych więc zachęcam do zabawy.
Do pobrania: wsdl-first.zip

Działa chyba ok :)
2010-02-03 o 12:44:24
Strasznie dużo papierkowej roboty macie tam w tej Javie :)
2010-02-04 o 03:35:51