Od jakiegoś czasu w pracy do tworzenia usług sieciowych korzystam z Apache CXF. Jako że biblioteka jest stosunkowo nowa i nie najlepiej udokumentowana postanowiłem przedstawić na blogu jak wygląda proces tworzenia.
CXF jest połączeniem kilku bibliotek – YOKO, Celtixa oraz XFire. Każda z nich wcześniej realizowała pewien fragment obecnej funkcjonalności CXF – YOKO obsługuje Corbę a XFire usługi sieciowe. Obecne CXF jest gotowy do używania “produkcyjnego”, ponieważ niedawno wyszedł z fazy inkubacji. 🙂
CXF ma dosyć elastyczną budowę. Zgodnie z dokumentacją można wyróżnić najważniejsze składowe:
CXF oferuje infrastrukturę konieczną do budowania usług, z najważniejszy zalet można wymienić:
Z dodatkowych zalet, mogę dodać – bardzo łatwą integrację ze Springiem.
Do budowania projektów będziemy używać Mavena. Implementowana usługa będzie oparta o frontend JAX-WS z ręcznie pisanym deskryptorem usługi (WSDL first). Jakkolwiek w bardzo prosty sposób można odwrócić kolejność i przy pomocy pluginu CXF do Mavena wygenerować deskryptor.
Struktura projektów będzie następująca:
Poniżej znajduje się deskryptor projektu, który jest używany do budowania całości.
<?xml version="1.0" encoding="UTF-8"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <!-- Informacja dla Mavena --> <modelVersion>4.0.0</modelVersion> <!-- Identyfikacja projektu --> <groupId>org.code-house.cxf</groupId> <artifactId>parent</artifactId> <name>Code House.Org - CXF</name> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <description>Rodzic projektu, zawiera wszystkie moduly.</description> <!-- Składowe projektu --> <modules> <module>cxf-client</module> <module>cxf-contract</module> <module>cxf-server</module> <module>cxf-webapp</module> </modules> <!-- Definicje zmiennych dostępne również w modułach --> <properties> <code-house.cxf.version>2.1.1</code-house.cxf.version> <code-house.jaxb.version>2.1.3</code-house.jaxb.version> <code-house.spring.version>2.5.4</code-house.spring.version> </properties> <build> <plugins> <!-- Konfiguracja kompilatora --> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </build> <reporting> <!--Wycięte :) --> </reporting> <!-- Predefiniowane wersje bibliotek --> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${code-house.cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${code-house.cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>${code-house.cxf.version}</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>${code-house.jaxb.version}</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.1</version> </dependency> </dependencies> </dependencyManagement> </project>
Zgodnie z tym, co napisałem wcześniej – przyjąłem podejście, że WSDL jest pisany ręcznie, głównie dlatego że dla większych projektów można w prosty sposób narzucić jakąś organizację i podział plików, z których są następnie generowane źródła.
Najistotniejsza wstawka, która powinna znaleźć się w pomie:
<!-- Generowanie kodu z deskryptora WSDL --> <plugin> <groupId>org.apache.cxf</groupId> <artifactId>cxf-codegen-plugin</artifactId> <executions> <execution> <phase>generate-sources</phase> <configuration> <sourceRoot>${basedir}/target/jaxws</sourceRoot> <wsdlOptions> <wsdlOption> <wsdl> ${basedir}/src/main/resources/maven.wsdl </wsdl> <extraargs> <extraarg>-quiet</extraarg> </extraargs> </wsdlOption> </wsdlOptions> </configuration> <goals> <goal>wsdl2java</goal> </goals> </execution> </executions> </plugin>
Po dodaniu tej wstawki do sekcji build/plugins możemy przejść do tworzenia deskryptora usługi. W moim przypadku przyjąłem następujący podział:
Każdy z tych plików ma inną przestrzeń nazw.
<?xml version="1.0" encoding="UTF-8"?> <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://code-house.org/services/maven" xmlns:types="http://code-house.org/services/maven/types" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://code-house.org/services/maven" name="MavenServices" > <wsdl:types> <xsd:schema targetNamespace="http://code-house.org/services/maven/types"> <xsd:include schemaLocation="types.xsd" /> </xsd:schema> </wsdl:types> <wsdl:message name="findArtifactRequest"> <wsdl:part name="request" type="types:FindArtifactRequest" /> </wsdl:message> <wsdl:message name="findArtifactResponse"> <wsdl:part name="response" type="types:FindArtifactRespose" /> </wsdl:message> <wsdl:portType name="MavenArtifactType"> <wsdl:operation name="findArtifact"> <wsdl:input message="tns:findArtifactRequest" /> <wsdl:output message="tns:findArtifactResponse" /> </wsdl:operation> </wsdl:portType> <wsdl:binding name="MavenArtifactSOAP" type="tns:MavenArtifactType"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="findArtifact"> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="MavenServices"> <wsdl:port binding="tns:MavenArtifactSOAP" name="Maven Services"> <soap:address location="http://localhost:8080/webapp/services/maven" /> </wsdl:port> </wsdl:service> </wsdl:definitions>
<?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://code-house.org/services/maven/types" xmlns:tns="http://code-house.org/services/maven/types" xmlns:def="http://code-house.org/services/maven/definition" elementFormDefault="qualified"> <import schemaLocation="definition.xsd" namespace="http://code-house.org/services/maven/definition" /> <complexType name="FindArtifactRequest"> <sequence> <element name="query" type="def:ArtifactInfo" /> </sequence> </complexType> <complexType name="FindArtifactRespose"> <sequence> <element name="downloadURL" type="anyURI" /> </sequence> </complexType> </schema>
<?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://code-house.org/services/maven/definition" xmlns:tns="http://code-house.org/services/maven/definition" elementFormDefault="qualified"> <complexType name="ArtifactInfo"> <sequence> <element name="groupId" type="string" /> <element name="artifactId" type="string" /> <element name="version" type="string" minOccurs="0" /> <element name="classifier" type="tns:Classifier" minOccurs="0" /> <element name="packaging" type="tns:Packaging" minOccurs="0" /> <element name="type" type="tns:Type" minOccurs="0" /> </sequence> </complexType> <simpleType name="Type"> <restriction base="string"> <enumeration value="dll" /> <enumeration value="so" /> </restriction> </simpleType> <simpleType name="Classifier"> <restriction base="string"> <enumeration value="sources" /> <enumeration value="javadoc" /> <enumeration value="resources" /> </restriction> </simpleType> <simpleType name="Packaging"> <restriction base="string"> <enumeration value="pom" /> <enumeration value="jar" /> <enumeration value="war" /> <enumeration value="bundle" /> </restriction> </simpleType> </schema>
Po odpaleniu polecenia mvn:install powinniśmy otrzymać w konsoli fragment podobny do tego:
[INFO] [cxf-codegen:wsdl2java {execution: default}]
Jest to informacja, że plugin CXF został poprawnie skonfigurowany i uruchomiony.
Sercem naszej usługi jest oczywiście jej implementacja dlatego też nie możemy obejść się bez niej. 🙂 Projekt ten ma tylko dwie zależności – contract oraz artefakt cxf-rt-frontend-jaxws.
Konfiguracja serwera odbywa się w oparciu o springa:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd "> <!-- Dodatkowy bean zawierający właściwą implementację logiki związanej z wyszukiwaniem --> <bean id="service" class="org.code_house.services.maven.DummyMavenSearchServiceImpl" /> <!-- Bean będący implementacją usługi jako takiej - obsługujący wejście/wyjście --> <bean id="mavenService" class="org.code_house.services.maven.MavenArtifactTypeImpl"> <property name="service" ref="service" /> </bean> <!-- Konfiguracja endpointu CXFa --> <jaxws:endpoint address="maven" id="jaxwsMavenService" implementor="#mavenService" /> </beans>
Serwer zawiera w zasadzie niewiele kodu, oto i on:
package org.code_house.services.maven; import java.net.URISyntaxException; import javax.jws.WebService; import org.code_house.services.maven.definition.ArtifactInfo; import org.code_house.services.maven.types.FindArtifactRequest; import org.code_house.services.maven.types.FindArtifactRespose; /** * Implementacja usługi. */ @WebService(serviceName = "MavenService", endpointInterface = "org.code_house.services.maven.MavenArtifactType", targetNamespace = "http://code-house.org/services/maven" ) public class MavenArtifactTypeImpl implements MavenArtifactType { /** * Bean zawierający implementację logiki biznesowej. */ private MavenSearchService service; public FindArtifactRespose findArtifact(FindArtifactRequest request) { ArtifactInfo info = request.getQuery(); // pobranie struktury przekazanej od klienta // sformuowanie odpowiedzi FindArtifactRespose response = new FindArtifactRespose(); try { response.setDownloadURL(service.find(info).toURI().toString()); } catch (URISyntaxException e) { throw new RuntimeException(e); } return response; } /** * Ustawienie wartości pola service. * * @param service Nowa wartość pola service. */ public void setService(MavenSearchService service) { this.service = service; } }
Dodatkowy kod, który nie jest konieczny do implementacji usługi to definicja interfejsu MavenSearchService. Dzięki zastosowaniu takiego rozwiązania można w prostszy sposób testować działanie usługi poprzez przekazywanie jej mocka stworzonego np. przy pomocy easy mocka. Kod takiego testu pominąłem ze względu na to, że notka i tak już jest za długa w tym momencie a jesteśmy ledwo w połowie drogi. 🙂
Przykładowa implementacja wcześniej wspomnianego intefejsu nie zawiera żadnej logiki i zawsze zwraca tą samą wartość.
package org.code_house.services.maven; import java.net.MalformedURLException; import java.net.URL; import org.code_house.services.maven.definition.ArtifactInfo; // Najprostsza implementacja tylko po to żeby sprawdzić działanie usługi public class DummyMavenSearchServiceImpl implements MavenSearchService { public URL find(ArtifactInfo info) { try { return new URL("http://repo1.maven.org/maven2/org/apache/apache/4/apache-4.pom"); } catch (MalformedURLException e) { throw new RuntimeException(e); } } }
Zanim odpalimy serwer konieczna będzie jeszcze jedna rzecz – konfiguracja transporu, w naszym przypadku servletu CXF.
Webapp posiada bezpośrednie zależności do 2 artefaktów CXFa: cxf-rt-transports-http oraz cxf-rt-bindings-http. Druga jest opcjonalna, bez niej CXF nie będzie wyświetlał dostępnych usług w postaci tabelki html.
Przydatna może być wtyczka jetty (sekcja build/plugins), która pozwala na uruchomienie aplikacji bez konieczności instalowania kontenera servletów:
<plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.11</version> <configuration> <scanIntervalSeconds>10</scanIntervalSeconds> <connectors> <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector"> <port>8080</port> <maxIdleTime>60000</maxIdleTime> </connector> </connectors> </configuration> </plugin>
Ostatnia zależność odnosi się do implementacji usługi – czyli naszego artefaktu server. Najistotniejsze elementy konfiguracji webappa to web.xml oraz cxf-beans.xml.
Warto zauważyć w poniższym listingu fajną możliwość, którą daje nam Spring w postaci wildcardów określających położenie konfiguracji – w tym przypadku classpath:/module/*-context.xml. Oznacza to, że Spring przeskanuje wszystkie biblioteki od których zależy projekt i dołączy ich konfigurację do głownej, dzięki czemu będziemy mogli uzyskać bardziej modułową i elastyczną budowę aplikacji. Może Dynamic Modules to nie jest ale na codzień w zupełności wystarcza :).
<?xml version="1.0" encoding="UTF-8"?> <web-app id="ocs" version="2.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation=" http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" > <!-- Informacje nazewnicze dla kontenera --> <display-name>Maven Search Services :: Webapp</display-name> <description>Frontend indeksera.</description> <!-- Lokalizacje plików konfiguracyjnych --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> <!-- Konfiguracja zabezpieczeń opartych o spring secuirty --> /WEB-INF/security.xml <!-- Konfiguracja CXF --> /WEB-INF/cxf-beans.xml <!-- Konfiguracja kontekstu --> /WEB-INF/applicationContext.xml <!-- Wczytanie konfiguracji modułów --> classpath:/module/*-context.xml </param-value> </context-param> <!-- Servlet obsługujący usługi sieciowe --> <servlet> <servlet-name>CXFServlet</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- Mapowanie adresu usług sieciowych --> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping> <!-- Inicjowanie kontekstu springa --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
Plik ten zawiera definicje konfiguracyjne CXFa. W poniższej formie włączana jest tylko część modułów CXF w celu zmniejszenia użycia zasobów.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:cxf="http://cxf.apache.org/core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <!-- Moduły CXF-a --> <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-extension-http-binding.xml"/> <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/> </beans>
Pozostałe pliki konfiguracyjne są praktycznie puste, ponieważ tyczą się obszarów niezwiązanych z tematem tego wpisu. Po tym wszystkim możemy uruchomić naszą aplikację poleceniem mvn jetty:run. Po wejściu na adres http://localhost:8080/webapp/services naszym oczom powinna ukazać się lista podobna do poniższej:
Nie wiem niestety dlaczego CXF generuje zły adres usługi (pomijający mapowanie servletu) oraz ma problemy z wystawieniem WSDLa. Jakkolwiek i bez tego wszystko działa poprawnie – znaczy wywoływanie usług rzecz jasna. 🙂
W tym momencie możemy uruchomić Soap UI i wykonać jedyną dostępną metodę – findArtifact.
Ostatnim z elementów, który pozostał do omówienia jest klient. Niestety z racji na to, że ta nota już się wystarczająco rozrosła zrobię to w kolejnym wpisie. Mam nadzieję, że nota ta nieco ułatwi przyszłym użytkownikom CXFa jego poznanie. 🙂 Dla tych, którym nie chce się wpisywać tego wszystkiego w edytorze zamieszczam źródła razem z działającym klientem. Projekty nie są nadzwyczajnie dopieszczone, ale w zupełności wystarczą do startu. Znajduje się też tam w pełni działający klient stworzonej usługi. Pozdrawiam i życzę miłej zabawy!
11 Responses to Budowanie usługi sieciowej w oparciu o Apache CXF
cysiaczek
July 24, 2008 at 6:09 pm
Uh, myślę, że ludzie przestali nadążać za Tobą i dlatego nikt nie komentuje… będę tym odważnym, który się przyzna – odpadam na tym etapie 😀
Pozdrawiam.
acid
July 24, 2008 at 7:28 pm
heh.. ok spoko fajnie ale o co chodzi? do czego to? co w tym fajnego aby opisywać na blogu? może jako techniczna notka jest OK. ale 99% ludzi nie wie o co chodzi :]
takitam
July 24, 2008 at 11:49 pm
Dobre wprowadzenie do CXF. Pozdrawiam.
@acid – hmm… chyba jestem tym 1% 😉
Łukasz Dywicki
July 25, 2008 at 9:22 am
Cysiaczek i acid notka ta jest bardzo mocno związana z Javą stąd może wydawać się Wam nieco zakręcona. Zarówno Maven, Spring jak i CXF nie ma swojego odpowiednika w PHP stąd też trudno mi to przełożyć. Gdybym miał w skrócie powiedzieć co i jak – Maven to taki Ant tylko że o wiele bardziej.. rozbudowany może nie będzie to obrazowe porównanie ale to tak jak by zestawić Javę i C++. CXF to odpowiednik niegdysiejszego NuSOAP dla PHP, pozwala budować usługi i z nich korzystać, fakt faktem jest dosyć złożony z racji na to, że obsługuje nie tylko Web Services.
takitam cieszę się że ktoś jeszcze to czyta. 🙂
Strzałek
July 26, 2008 at 10:57 pm
cysiaczek, acid – tematyka javowa nie znana nam.
splatch – ostanio w tej twojej javie więcej xml niż javy 😉
activey
July 29, 2008 at 10:04 am
[mroczny glos w tle] … join ussss …
Zobowiązany :- )
July 30, 2008 at 2:04 pm
Hej, dzięki wielkie za ten opis.
Dobre wprowadzenie do tematu. 🙂
Pozdrawiam.
luki
July 31, 2008 at 3:28 pm
Witam,
Prosze o wyjasnienie danej sprawy jak to zrobic…
Mam wygenerowana juz klase z danego PortType-a i teraz chce ja za pomoca CXF-a odpalic…
Co mam zrobic czy wygenerowac z niej webservice, poprostu jak ta klase wystawic na server za pomoca CXF-a
Prosze o odpowiedz
Łukasz Dywicki
August 8, 2008 at 7:09 pm
luki nie do końca rozumiem Twoje pytania, jeśli masz wygenerowany interfejs to po dopisaniu implementacji tworzysz kontekst i uruchamiasz z wybranym transportem.
Budowanie klienta usługi sieciowej w oparciu o Apache CXF | Splatch's devblog
September 3, 2008 at 9:28 am
[…] nawiązaniu do poprzedniej noty o CXFie, którą napisałem jakiś czas temu, gonię aby uzupełnić brak konfiguracji klienta. Sam proces […]
L
January 11, 2012 at 11:40 pm
Właśnie czegoś takiego potrzebowałem, dzięki za tutorial