W nawiązaniu do poprzedniej noty o CXFie, którą napisałem jakiś czas temu, gonię aby uzupełnić brak konfiguracji klienta. Sam proces jest bardzo zbliżony do tworzenia klienta w oparciu o XFire. Nie jest wymagana duża ilość kodu Javy, a w zasadzie tylko dwa pliki XML (client.xml, myservice.xml).
Pierwszy z nich odpowiada za wczytanie wymaganych rozszerzeń CXFa oraz definicję bazowej konfiguracji fabryki z interceptorami. W interceptorach możemy skonfigurować logowanie, obsługę załączników czy standardów WS-Security etc. Wszystkie te ustawienia będą dziedziczone, a fabryki docelowych usług będą dodawać tylko adres, do odpytywania. Na koniec bean klienta będzie miał określony autowire by nie przekazywać mu wszystkich własności.
Oto najważniejsze wstawki kodu oraz ich opis:
<?xml version="1.0" encoding="UTF-8"?> <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"> <parent> <groupId>org.code-house.cxf</groupId> <artifactId>parent</artifactId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>org.code-house.cxf</groupId> <artifactId>client</artifactId> <version>1.0-SNAPSHOT</version> <name>Code House.Org - CXF - Client</name> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>org.code-house.cxf</groupId> <artifactId>contract</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>${code-house.cxf.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${code-house.spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${code-house.spring.version}</version> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${code-house.spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${code-house.spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${code-house.spring.version}</version> </dependency> </dependencies> </dependencyManagement> </project>
Deskryptor nie jest zbyt złożony, istotny jest tylko kawałek z kontraktem, który jak wskazuje nazwa jest definicją używanych typów:
<dependency> <groupId>org.code-house.cxf</groupId> <artifactId>contract</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
Teraz kolej na jedyną wstawkę Javy, która się pojawia w projekcie. Jest to zwykły bean, który będzie miał później wstrzykiwane obiekty pośredniczące w wywoływaniu usług.
package org.code_house.cxf.client; import org.code_house.services.maven.MavenArtifactType; /** * Klient usług Code-House. * * @author Łukasz Dywicki <a href="splatch@code-house.org">email</a> * * $Id$ */ public class Client { /** * Usługa do obsługi wyszukiwania artefaktów Mavena. */ private MavenArtifactType maven; /** * Pobranie wartości pola maven. * * @return Wartość maven. */ public MavenArtifactType getMaven() { return maven; } /** * Ustawienie wartości pola maven. * * @param maven Nowa wartość pola maven. */ public void setMaven(MavenArtifactType maven) { this.maven = maven; } }
Resztę magii załatwia Spring:
<?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" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <!-- Konfiguracja placeholderów (znaczy wstawek ${}) --> <context:property-placeholder location="classpath:client.properties" /> <!-- Importy rzeczy koniecznych do pracy CXF --> <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-jetty.xml" /> <!-- Konfiguracja poszczególnych usług wstrzykiwanych do klienta --> <import resource="classpath:services/*.xml" /> <!-- Bean zawierający referencje do wygenerowanych klientów usług --> <bean id="client" class="org.code_.housecxf.client.Client" autowire="autodetect" /> <!-- Bazowa konfiguracja fabryk - obiektów tworzących stuby klientów w runtime --> <bean id="baseClientFactory" abstract="true" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean"> <property name="username" value="${org.code_house.cxf.user}" /> <property name="password" value="${org.code_house.cxf.password}" /> <property name="inInterceptors"> <list> <ref bean="logIn" /> </list> </property> <property name="outInterceptors"> <list> <ref bean="logOut" /> </list> </property> </bean> <!-- Loggery dla CXF --> <bean id="logIn" class="org.apache.cxf.interceptor.LoggingInInterceptor" /> <bean id="logOut" class="org.apache.cxf.interceptor.LoggingOutInterceptor" /> </beans>
Zgodnie ze wstawką w linii 20 konieczna jest jeszcze konfiguracja usługi. Sztuczka polega na użyciu części konfiguracji zdefiniowanej wcześniej – baseClientFactory.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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 "> <!-- Konfiguracja stuba dla usługi wyszukiwania artefaktów --> <bean id="mavenService" class="org.code_house.services.maven.MavenArtifactType" factory-bean="mavenServiceFactory" factory-method="create" /> <!-- Fabryka usługi wyszukującej --> <bean id="mavenServiceFactory" parent="baseClientFactory"> <!-- jedyne parametry jakich potrzebujemy --> <property name="serviceClass" value="org.code_house.services.maven.MavenArtifactType" /> <property name="address" value="${org.code_house.cxf.service.maven}" /> </bean> </beans>
Ustawienia, które mogą ulec zmianie, to znaczy użytkownik, hasło oraz adres usługi są wyodrębnione do pliku client.properties:
# Placeholdery dla kontekstow springa
# Adresy uslug
server.port = 8080
host = localhost
org.code_house.cxf.service.maven http://${host}:${server.port}/webapp/services/maven
# Autoryzacja
org.code_house.cxf.user
org.code_house.cxf.password
No i na koniec opcjonalny test, który odpytuje usługę:
package org.code_house.cxf.client; import org.code_house.services.maven.definition.ArtifactInfo; import org.code_house.services.maven.types.FindArtifactRequest; import org.code_house.services.maven.types.FindArtifactRespose; import org.springframework.test.AbstractDependencyInjectionSpringContextTests; /** * Proste wywołanie klasy klienta. * * @author Łukasz Dywicki <a href="ldywicki@pocztowy.pl">email</a> * * $Id$ */ public class MainTest extends AbstractDependencyInjectionSpringContextTests { /** * Wstrzyknięty klient. */ private Client client; @Override protected String[] getConfigLocations() { return new String[] {"classpath:client.xml"}; } public void testOne() { FindArtifactRequest request = new FindArtifactRequest(); ArtifactInfo artifact = new ArtifactInfo(); artifact.setGroupId("org.code_house.cxf"); artifact.setArtifactId("contract"); request.setQuery(artifact); FindArtifactRespose respose = client.getMaven().findArtifact(request); System.out.println(respose.getDownloadURL()); } /** * Ustawienie wartości pola client. * * @param client Nowa wartość pola client. */ public void setClient(Client client) { this.client = client; } }
To by było na tyle. Cały działający kod projektu jest już zamieszczony przy poprzedniej nocie, paczka ze wszystkimi listingami gotowa do pobrania.
Teraz chyba pora zacząć opisywać mechanizmy Springa. 🙂
5 Responses to Budowanie klienta usługi sieciowej w oparciu o Apache CXF
MF
September 30, 2008 at 4:38 pm
cze,
Lukasz, wiesz moze jak wykonac prawidlowo call do ws w loop’ie?
tj. zaluzmy ze z jakiegos powodu chce w nieskonczonosc robic
for(;;)
findArtifact(request)
powinno to byc robione efektywnie [np. moze cxf uzywa http keep-alive?]
czy jesli zerwie sie polaczenie http, bede mial je odtworzone czy powinienem w loop’ie za kazdym razem tworzsyc port?
dzieki
Łukasz Dywicki
October 8, 2008 at 2:05 pm
Witam,
Całkiem przypadkiem trafiłem na rozwiązanie Twojego problemu. CXF domyślnie używa connection=close, jakkolwiek można to zmienić używając dodatkowej przestrzeni nazw http-con. Dłuższy opis jest dostępny na stronie CXFa.
fidd
April 17, 2010 at 6:59 pm
Problem z linkiem:
http://media.dywicki.pl/blog/cxf/cxf.zip
Mozna cos z tym zrobic?
Łukasz Dywicki
April 18, 2010 at 10:58 am
Hej fidd, niestety plik ten przepadł podczas porządków na serwerze. Jedyne co pozostało to kod w listingach.
ed
September 25, 2012 at 4:09 pm
Chciałbym napisac aplikacje kliencka, ktora laczy sie z webserwisem pod
adresem
http://www.webservicex.com/globalweather.asmx?wsdl
Jej zadaniem jest wyswietlenie prognozy pogody dla wybranego miasta.
Liste miast mozna uzyskac po wprowadzeniu nazwy kraju w jezyku angielskim.
Opis operacji znajduje sie tutaj:
http://www.webservicex.com/globalweather.asmx
Chciałbym aby program działał z serwerem tomcat i korzystał z CXF.
Jestem w te klocki czy mógłbym liczyć na jakąś pomoc może jakiś krótki tutorial ??
Z góry bardzo dziękuje.
Pozdrawiam