Some of posts from this blog has been moved to dywicki.pl. You will be automatically redirected to new blog if you would submit comment.
New posts are published on dywicki.pl, this blog contains old content and it is not continued.

Niektóre posty z tego bloga zostały przeniesione do dywicki.pl. Zostaniesz automatycznie przekierowany jeśli bedzięsz chciał dodać komentarz.
Nowe posty sa publikowane na dywicki.pl, ten blog zawiera stare treści i nie jest kontynuowany.

Budowanie klienta usługi sieciowej w oparciu o Apache CXF

Filed under CXF,Framework,Java by

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. :)

4 responses so far

4 Responses to “Budowanie klienta usługi sieciowej w oparciu o Apache CXF”

  1. MF says:

    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

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

    XML:
    1. <beans xmlns="http://www.springframework.org/schema/beans"
    2.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3.     xmlns:http="http://cxf.apache.org/transports/http/configuration"
    4.     xsi:schemaLocation="
    5.     ">
    6.  
    7.     <http-conf:conduit name="{http://apache.org/hello_world_soap_http}SoapPort.http-conduit">
    8.         <http-conf:client Connection="Keep-Alive"
    9.             MaxRetransmits="1"
    10.             AllowChunking="false" />
    11.     </http-conf:conduit>
    12.  
    13. </beans>

  3. fidd says:

    Problem z linkiem:

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

    Mozna cos z tym zrobic?

  4. Hej fidd, niestety plik ten przepadł podczas porządków na serwerze. Jedyne co pozostało to kod w listingach.

Leave a Reply