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
lipiec 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
lipiec 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
lipiec 24, 2008 at 11:49 pm
Dobre wprowadzenie do CXF. Pozdrawiam.
@acid – hmm… chyba jestem tym 1%
Łukasz Dywicki
lipiec 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
lipiec 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
lipiec 29, 2008 at 10:04 am
[mroczny glos w tle] … join ussss …
Zobowiązany :- )
lipiec 30, 2008 at 2:04 pm
Hej, dzięki wielkie za ten opis.
Dobre wprowadzenie do tematu.
Pozdrawiam.
luki
lipiec 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
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
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
at 11:40 pm
Właśnie czegoś takiego potrzebowałem, dzięki za tutorial