Budowanie klienta usługi sieciowej w oparciu o Apache CXF

3 Sep
2008

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

Avatar

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

Avatar

Ł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.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:http="http://cxf.apache.org/transports/http/configuration"
    xsi:schemaLocation="
        http://cxf.apache.org/transports/http/configuration
        http://cxf.apache.org/schemas/configuration/http-conf.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
    ">

    <http-conf:conduit name="{http://apache.org/hello_world_soap_http}SoapPort.http-conduit">
        <http-conf:client Connection="Keep-Alive"
            MaxRetransmits="1"
            AllowChunking="false" />
    </http-conf:conduit>

</beans>

Avatar

fidd

April 17, 2010 at 6:59 pm

Problem z linkiem:

http://media.dywicki.pl/blog/cxf/cxf.zip

Mozna cos z tym zrobic?

Avatar

Ł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.

Avatar

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

Comment Form

top