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. :)
Architektura
CXF ma dosyć elastyczną budowę. Zgodnie z dokumentacją można wyróżnić najważniejsze składowe:
- Bus, jest trzonem architektury CXF w którym definiuje i konfiguruje się rozszerzenia.
- Messaging & Interceptors, zapewniają niskopoziomowy dostęp do komunikatów oraz warstwę na której jest oparta większość funkcjonalności.
- Front ends, frontendy są interfejsami programistycznymi do tworzenia usług (np. JAX-WS).
- Services, usługi zapewniają model wraz z opisem
- Bidings, element ten jest odpowiedzialny za obsługę konkretnego protokołu (SOAP, REST, Corba etc).
- Transports, warstwa abstrakcji ułatwiająca zmianę sposobu transportu do/z usług.
Markieting :)
CXF oferuje infrastrukturę konieczną do budowania usług, z najważniejszy zalet można wymienić:
- Wsparcie dla różnych protokołów.
- Obsługa standardów WS-*, tj. WS-Addressing, WS-Security, WS-ReliableMessaging, oraz WS-Policy.
- Obsługa wielu transportów.
- Dołączane data-bindingi (np JAXB, Aegis).
- Jasny podział front endów takich jak JAX-WS od najważniejszego kodu.
- Wysoka wydajność.
- Możliwość osadzania w różnych środowiskach.
Z dodatkowych zalet, mogę dodać - bardzo łatwą integrację ze Springiem.
Pierwsza usługa
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:
-
parent
Rodzic projektu ze zdefiniowanymi wersjami bibliotek i raportami.
-
contract
Definicje używane zarówno przez klienta jak i serwer - WSDL oraz konfiguracja pluginu CXF.
-
client
Prosta biblioteka kliencka oparta o mechanizmy CXFa (JaxWSProxyFactoryBean).
-
server
Przykładowa implementacja usługi z bardzo prostym wykorzystaniem Springa.
-
webapp
Konfiguracja transporty CXF - w tym konkretnym przypadku servletu CXF.
-
Parent
Poniżej znajduje się deskryptor projektu, który jest używany do budowania całości. [sourcecode lang=“xml”] 4.0.0org.code-house.cxfparentCode House.Org - CXF1.0-SNAPSHOTpomRodzic projektu, zawiera wszystkie moduly.cxf-clientcxf-contractcxf-servercxf-webapp2.1.12.1.32.5.4maven-compiler-plugin1.5 1.5org.apache.cxfcxf-rt-frontend-jaxws${code-house.cxf.version}org.apache.cxfcxf-rt-transports-http${code-house.cxf.version}org.apache.cxfcxf-rt-transports-http-jetty${code-house.cxf.version}com.sun.xml.bindjaxb-impl${code-house.jaxb.version}javax.xml.bindjaxb-api2.1 [/sourcecode]
Contract
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: [sourcecode lang=“xml”] org.apache.cxfcxf-codegen-plugingenerate-sources${basedir}/target/jaxws ${basedir}/src/main/resources/maven.wsdl -quietwsdl2java [/sourcecode]
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ł:
- maven.wsdl - definicje metod oraz komunikatów w rozumieniu WSDL (messages). Można z powodzeniem wyłączyć z tego pliku same koperty i pozostawić metody a to co potrzebne włączyć dyrektywą wsdl:import.
- types.xsd - typy używane do komunikacji - zazwyczaj pary request+response używane bezpośrednio w definiowaniu elementów wsdl:part.
- definition.xsd - definicje typów złożonych, niezależnych od usług, tj. opis domain-modelu z którym usługa pracuje.
Każdy z tych plików ma inną przestrzeń nazw.
maven.wsdl
[sourcecode lang=“xml”]
[/sourcecode]
types.xsd
[sourcecode lang=“xml”]
[/sourcecode]
definition.xsd
[sourcecode lang=“xml”]
[/sourcecode]
Po odpaleniu polecenia mvn:install powinniśmy otrzymać w konsoli fragment podobny do tego: [sourcecode] [INFO] [cxf-codegen:wsdl2java {execution: default}] [/sourcecode] Jest to informacja, że plugin CXF został poprawnie skonfigurowany i uruchomiony.
Serwer
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: [sourcecode lang=“xml”]
[/sourcecode]
Serwer zawiera w zasadzie niewiele kodu, oto i on: [sourcecode lang=“java”] 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; } } [/sourcecode]
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ść. [sourcecode lang=“java”] 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); } }
} [/sourcecode]
Zanim odpalimy serwer konieczna będzie jeszcze jedna rzecz - konfiguracja transporu, w naszym przypadku servletu CXF.
Webapp
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: [sourcecode lang=“xml”] org.mortbay.jettymaven-jetty-plugin6.1.1110808060000 [/sourcecode]
Ostatnia zależność odnosi się do implementacji usługi - czyli naszego artefaktu server. Najistotniejsze elementy konfiguracji webappa to web.xml oraz cxf-beans.xml.
web.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 :).
[sourcecode lang=“xml”] Maven Search Services :: WebappFrontend indeksera.contextConfigLocation /WEB-INF/security.xml
/WEB-INF/cxf-beans.xml
/WEB-INF/applicationContext.xml
classpath:/module/*-context.xml CXFServletorg.apache.cxf.transport.servlet.CXFServlet1CXFServlet/services/*org.springframework.web.context.ContextLoaderListener [/sourcecode]
cxf-beans.xml
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. [sourcecode lang=“xml”]
[/sourcecode]
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.
Client
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!