Jak uniknąć konieczności określania lokalizacji WSDL w kliencie usługi sieciowej wygenerowanym przez CXF lub JAX-WS?

165

Kiedy generuję klienta webservice za pomocą wsdl2java z CXF (który generuje coś podobnego do wsimport), za pośrednictwem maven, moje usługi zaczynają się od takich kodów:

@WebServiceClient(name = "StatusManagement", 
                  wsdlLocation = "c:/some_absolute_path_to_a_wsdl_file.wsdl",
                  targetNamespace = "http://tempuri.org/") 
public class StatusManagement extends Service {

    public final static URL WSDL_LOCATION;
    public final static QName SERVICE = new QName("http://tempuri.org/", "StatusManagement");
    public final static QName WSHttpBindingIStatus = new QName("http://tempuri.org/", "WSHttpBinding_IStatus");
    static {
        URL url = null;
        try {
            url = new URL("c:/some_absolute_path_to_a_wsdl_file.wsdl");
        } catch (MalformedURLException e) {
            System.err.println("Can not initialize the default wsdl from c:/some_absolute_path_to_a_wsdl_file.wsdl");
            // e.printStackTrace();
        }
        WSDL_LOCATION = url;
    }

Zakodowana na stałe ścieżka absolutna naprawdę jest do niczego. Wygenerowana klasa nie będzie działać na żadnym innym komputerze niż mój.

Pierwszym pomysłem jest umieszczenie pliku WSDL (plus wszystko, co importuje, inne WSDL i XSD) gdzieś w pliku jar i to w ścieżce klas. Ale chcemy tego uniknąć. Ponieważ wszystko to zostało wygenerowane przez CXF i JAXB oparte na plikach WSDL i XSD, nie widzimy potrzeby znajomości WSDL w czasie wykonywania.

Atrybut wsdlLocation ma na celu przesłonięcie położenia WSDL (przynajmniej to gdzieś przeczytałem) i jego domyślną wartością jest „”. Ponieważ używamy maven, próbowaliśmy dołączyć <wsdlLocation></wsdlLocation>do konfiguracji CXF, aby spróbować zmusić generator źródłowy do pozostawienia pustego miejsca wsdlLocation. Jednak to po prostu powoduje, że ignoruje znacznik XML, ponieważ jest pusty. Zrobiliśmy naprawdę brzydki, haniebny hack, używając <wsdlLocation>" + "</wsdlLocation>.

To zmienia również inne miejsca:

@WebServiceClient(name = "StatusManagement", 
                  wsdlLocation = "" + "",
                  targetNamespace = "http://tempuri.org/") 
public class StatusManagement extends Service {

    public final static URL WSDL_LOCATION;
    public final static QName SERVICE = new QName("http://tempuri.org/", "StatusManagement");
    public final static QName WSHttpBindingIStatus = new QName("http://tempuri.org/", "WSHttpBinding_IStatus");
    static {
        URL url = null;
        try {
            url = new URL("" + "");
        } catch (MalformedURLException e) {
            System.err.println("Can not initialize the default wsdl from " + "");
            // e.printStackTrace();
        }
        WSDL_LOCATION = url;
    }

Więc moje pytania to:

  1. Czy naprawdę potrzebujemy lokalizacji WSDL, nawet jeśli wszystkie klasy zostały wygenerowane przez CXF i JAXB? Jeśli tak, dlaczego?

  2. Jeśli naprawdę nie potrzebujemy lokalizacji WSDL, jaki jest właściwy i czysty sposób, aby CXF jej nie generował i całkowicie jej unikał?

  3. Jakie złe skutki uboczne moglibyśmy uzyskać dzięki temu włamaniu? Nadal nie możemy tego przetestować, aby zobaczyć, co się stanie, więc gdyby ktoś mógł powiedzieć z góry, byłoby miło.

Victor Stafusa
źródło

Odpowiedzi:

206

W końcu znalazłem dzisiaj właściwą odpowiedź na to pytanie.

<plugin>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-codegen-plugin</artifactId>
    <version>${cxf.version}</version>
    <executions>
        <execution>
            <id>generate-sources</id>
            <phase>generate-sources</phase>
            <configuration> 
                <sourceRoot>${project.build.directory}/generated-sources/cxf</sourceRoot>
                <wsdlOptions>
                    <wsdlOption>
                        <wsdl>${project.basedir}/src/main/resources/wsdl/FooService.wsdl</wsdl>
                        <wsdlLocation>classpath:wsdl/FooService.wsdl</wsdlLocation>
                    </wsdlOption>
                </wsdlOptions>
            </configuration>
            <goals>
                <goal>wsdl2java</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Zauważ, że ja prefiksem wartość w wsdlLocationzclasspath: . To mówi wtyczce, że wsdl będzie w ścieżce klas, a nie w ścieżce bezwzględnej. Następnie wygeneruje kod podobny do tego:

@WebServiceClient(name = "FooService", 
                  wsdlLocation = "classpath:wsdl/FooService.wsdl",
                  targetNamespace = "http://org/example/foo") 
public class Foo_Service extends Service {

    public final static URL WSDL_LOCATION;

    public final static QName SERVICE = new QName("http://org/example/foo", "Foo");
    public final static QName FooSOAPOverHTTP = new QName("http://org/example/foo", "Foo_SOAPOverHTTP");
    static {
        URL url = Foo_Service.class.getClassLoader().getResource("wsdl/FooService.wsdl");
        if (url == null) {
            java.util.logging.Logger.getLogger(Foo_Service.class.getName())
                .log(java.util.logging.Level.INFO, 
                     "Can not initialize the default wsdl from {0}", "classpath:wsdl/FooService.wsdl");
        }       
        WSDL_LOCATION = url;
    }

Zauważ, że działa to tylko z wersją 2.4.1 lub nowszą wtyczki cxf-codegen-plugin.

Kyle
źródło
8
Używając wtyczki JAX Maven zamiast CXF, pomiń classpath:w <wsdlLocation...wierszu.
Twilite
czy ktoś ma problem z przestrzenią nazw z kodem wygenerowanym powyższą metodą?
Narendra Jaggi
Czy musisz wymieniać każdy wsdl osobno, jeśli masz kilka? Czy można tego uniknąć?
pitseeker
21

Używamy

wsdlLocation = "WEB-INF/wsdl/WSDL.wsdl"

Innymi słowy, użyj ścieżki względnej do ścieżki klas.

Uważam, że WSDL może być potrzebny w czasie wykonywania do sprawdzania poprawności komunikatów podczas marszałka / unmarshal.

BPS
źródło
17

Dla tych, którzy używają org.jvnet.jax-ws-commons:jaxws-maven-plugindo generowania klienta z WSDL w czasie kompilacji:

  • Umieść WSDL gdzieś w swoim src/main/resources
  • Czy nie prefikswsdlLocation zclasspath:
  • Zrób prefiks wsdlLocationz/

Przykład:

  • WSDL jest przechowywany w /src/main/resources/foo/bar.wsdl
  • Skonfiguruj za jaxws-maven-pluginpomocą <wsdlDirectory>${basedir}/src/main/resources/foo</wsdlDirectory>i<wsdlLocation>/foo/bar.wsdl</wsdlLocation>
Martin Devillers
źródło
dlaczego nie używać przedrostka "wsdlLocation ze ścieżką klas", używam go i działa
Mohammad Sadegh Rafiei
9

1) W niektórych przypadkach tak. Jeśli WSDL zawiera takie elementy, jak zasady i takie, które kierują zachowaniem w czasie wykonywania, wówczas WSDL może być wymagany w czasie wykonywania. Artefakty nie są generowane dla rzeczy związanych z polityką i tym podobnych. Ponadto w niektórych niejasnych przypadkach RPC / Literal nie wszystkie potrzebne przestrzenie nazw są wyprowadzane w wygenerowanym kodzie (zgodnie ze specyfikacją). Zatem wsdl byłby dla nich potrzebny. Jednak niejasne przypadki.

2) Myślałem, że coś takiego zadziała. Jaka wersja CXF? To brzmi jak błąd. Możesz tam spróbować pustego łańcucha (tylko spacje). Nie jestem pewien, czy to działa, czy nie. To powiedziawszy, w swoim kodzie możesz użyć konstruktora, który pobiera adres URL WSDL i po prostu przekazuje null. WSDL nie zostanie użyty.

3) Tylko powyższe ograniczenia.

Daniel Kulp
źródło
Jest to najnowszy CXF 2.3.1. Wydany zaledwie 8 dni temu. Podanie wartości null jest dobrym pomysłem. Powinienem wcześniej zobaczyć tę oczywistą odpowiedź. Nadal spróbuję spacji.
Victor Stafusa
Nie, puste miejsca działają tak samo, jak nic. tj .: znacznik XML jest całkowicie ignorowany.
Victor Stafusa
5

Udało mi się wygenerować

static {
    WSDL_LOCATION = null;
}

konfigurując plik pom tak, aby miał wartość null dla wsdlurl:

    <plugin>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-codegen-plugin</artifactId>
        <executions>
            <execution>
                <id>generate-sources</id>
                <phase>generate-sources</phase>
                <configuration>
                    <sourceRoot>${basedir}/target/generated/src/main/java</sourceRoot>
                    <wsdlOptions>
                        <wsdlOption>
                            <wsdl>${basedir}/src/main/resources/service.wsdl</wsdl>
                            <extraargs>
                                <extraarg>-client</extraarg>
                                <extraarg>-wsdlLocation</extraarg>
                                <wsdlurl />
                            </extraargs>
                        </wsdlOption>
                    </wsdlOptions>
                </configuration>
                <goals>
                    <goal>wsdl2java</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
raisercostin
źródło
2
To rozwiązanie nie działało dla mnie z CXF 3.1.0. dostał błąd org.apache.cxf.tools.common.toolspec.parser.BadUsageException: Nieoczekiwana opcja: -wsdlLocation
Chandru
4

Czy to możliwe, że można uniknąć używania wsdl2java? Możesz od razu użyć interfejsów API CXF FrontEnd do wywołania usługi sieci Web SOAP. Jedynym haczykiem jest to, że musisz utworzyć SEI i VO po stronie klienta. Oto przykładowy kod.

package com.aranin.weblog4j.client;

import com.aranin.weblog4j.services.BookShelfService;
import com.aranin.weblog4j.vo.BookVO;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;

public class DemoClient {
    public static void main(String[] args){
        String serviceUrl = "http://localhost:8080/weblog4jdemo/bookshelfservice";
        JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
        factory.setServiceClass(BookShelfService.class);
        factory.setAddress(serviceUrl);
        BookShelfService bookService = (BookShelfService) factory.create();

        //insert book
        BookVO bookVO = new BookVO();
        bookVO.setAuthor("Issac Asimov");
        bookVO.setBookName("Foundation and Earth");

        String result = bookService.insertBook(bookVO);

        System.out.println("result : " + result);

        bookVO = new BookVO();
        bookVO.setAuthor("Issac Asimov");
        bookVO.setBookName("Foundation and Empire");

        result = bookService.insertBook(bookVO);

        System.out.println("result : " + result);

        bookVO = new BookVO();
        bookVO.setAuthor("Arthur C Clarke");
        bookVO.setBookName("Rama Revealed");

        result = bookService.insertBook(bookVO);

        System.out.println("result : " + result);

        //retrieve book

        bookVO = bookService.getBook("Foundation and Earth");

        System.out.println("book name : " + bookVO.getBookName());
        System.out.println("book author : " + bookVO.getAuthor());

    }
}

Możesz zobaczyć pełny samouczek tutaj http://weblog4j.com/2012/05/01/developing-soap-web-service-using-apache-cxf/

Niraj Singh
źródło
2
Pliki WSDL były niezwykle skomplikowane, więc użyliśmy autogeneracji jako sposobu na zapewnienie kompatybilności. Autogeneracja stworzyła równie niezwykle skomplikowane VO i SEI. Zdecydowaliśmy się użyć osobnego zestawu obiektów domeny całkowicie odsprzęgniętych od tych generowanych automatycznie, więc nie ingerowaliśmy w autogenerację, ani nie byliśmy przez nią ograniczani ani sterowani. Wygenerowane automatycznie VO były używane tylko w kontekście komunikacji usług i utrzymywaliśmy je jak najkrótsze. Innymi słowy, jednym z naszych problemów jest uniknięcie konieczności ręcznego kodowania i zarządzania wszystkimi funkcjami VO.
Victor Stafusa,
2
Zgadzam się z Victor, ponieważ ręczne konserwowanie VO może być stratą czasu i ryzykiem wystąpienia różnic, mniej lub bardziej widocznych i kwalifikowanych ... to jest właśnie cel wsdl2java, dlatego jest użyteczny i bezpieczny!
Donatello,
4

Aktualizacja dla CXF 3.1.7

W moim przypadku umieściłem pliki WSDL w src/main/resources i dodałem tę ścieżkę do moich zasobów w Eclipse (Kliknij prawym przyciskiem myszy Projekt-> Ścieżka kompilacji -> Konfiguruj ścieżkę budowania ...-> Źródło [karta] -> Dodaj folder).

Oto jak pomwygląda mój plik i jak widać NIE jest wsdlLocation potrzebna opcja:

       <plugin>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-codegen-plugin</artifactId>
            <version>${cxf.version}</version>
            <executions>
                <execution>
                    <id>generate-sources</id>
                    <phase>generate-sources</phase>
                    <configuration>
                        <sourceRoot>${project.build.directory}/generated/cxf</sourceRoot>
                        <wsdlOptions>
                            <wsdlOption>
                                <wsdl>classpath:wsdl/FOO_SERVICE.wsdl</wsdl>
                            </wsdlOption>
                        </wsdlOptions>
                    </configuration>
                    <goals>
                        <goal>wsdl2java</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

A oto wygenerowana usługa. Jak widać, adres URL jest pobierany z ClassLoader, a nie z bezwzględnej ścieżki pliku

@WebServiceClient(name = "EventService", 
              wsdlLocation = "classpath:wsdl/FOO_SERVICE.wsdl",
              targetNamespace = "http://www.sas.com/xml/schema/sas-svcs/rtdm-1.1/wsdl/") 
public class EventService extends Service {

public final static URL WSDL_LOCATION;

public final static QName SERVICE = new QName("http://www.sas.com/xml/schema/sas-svcs/rtdm-1.1/wsdl/", "EventService");
public final static QName EventPort = new QName("http://www.sas.com/xml/schema/sas-svcs/rtdm-1.1/wsdl/", "EventPort");
static {
    URL url = EventService.class.getClassLoader().getResource("wsdl/FOO_SERVICE.wsdl");
    if (url == null) {
        java.util.logging.Logger.getLogger(EventService.class.getName())
            .log(java.util.logging.Level.INFO, 
                 "Can not initialize the default wsdl from {0}", "classpath:wsdl/FOO_SERVICE.wsdl");
    }       
    WSDL_LOCATION = url;   
}
Mazy
źródło
<configuration> <sourceRoot>${basedir}/src/main/java/</sourceRoot> <wsdlRoot>${basedir}/src/main/resources/</wsdlRoot> <includes> <include>*.wsdl</include> </includes> </configuration> Dołączam wszystkie pliki .wsdl do ścieżki klasy, a następnie w jaki sposób mogę określić lokalizację wsdl, aby każdy wygenerowany plik .java zawierał odpowiednią ścieżkę .wsdl? Z góry dziękuję. @Mazy
Khalid Shah
2

Poważnie, najlepsza odpowiedź nie działa dla mnie. wypróbowano cxf.version 2.4.1 i 3.0.10. i generuj bezwzględną ścieżkę za każdym razem za pomocą wsdlLocation.

Moim rozwiązaniem jest użycie wsdl2javapolecenia w apache-cxf-3.0.10\bin\ with-wsdlLocation classpath:wsdl/QueryService.wsdl .

Szczegół:

    wsdl2java -encoding utf-8 -p com.jeiao.boss.testQueryService -impl -wsdlLocation classpath:wsdl/testQueryService.wsdl http://127.0.0.1:9999/platf/testQueryService?wsdl
jeiao
źródło
0

Rozwiązanie @Martin Devillers działa dobrze. Aby uzyskać kompletność, wykonaj następujące czynności:

  1. Umieść swój wsdl w katalogu zasobów, takim jak: src/main/resource
  2. W pliku pom dodaj zarówno wsdlDirectory, jak i wsdlLocation (nie przegap / na początku wsdlLocation), jak poniżej. Podczas gdy wsdlDirectory jest używany do generowania kodu, a wsdlLocation jest używany w czasie wykonywania do tworzenia dynamicznego serwera proxy.

    <wsdlDirectory>src/main/resources/mydir</wsdlDirectory>
    <wsdlLocation>/mydir/my.wsdl</wsdlLocation>
  3. Następnie w swoim kodzie java (z konstruktorem no-arg):

    MyPort myPort = new MyPortService().getMyPort();
  4. Oto pełna część generowania kodu w pliku pom, z płynnym interfejsem API w generowanym kodzie.

    <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxws-maven-plugin</artifactId>
    <version>2.5</version>
    
    <dependencies>
        <dependency>
            <groupId>org.jvnet.jaxb2_commons</groupId>
            <artifactId>jaxb2-fluent-api</artifactId>
            <version>3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.ws</groupId>
            <artifactId>jaxws-tools</artifactId>
            <version>2.3.0</version>
        </dependency>
    </dependencies>
    
    <executions>
        <execution>
            <id>wsdl-to-java-generator</id>
            <goals>
                <goal>wsimport</goal>
            </goals>
            <configuration>
                <xjcArgs>
                    <xjcArg>-Xfluent-api</xjcArg>
                </xjcArgs>
                <keep>true</keep>
                <wsdlDirectory>src/main/resources/package</wsdlDirectory>
                <wsdlLocation>/package/my.wsdl</wsdlLocation>
                <sourceDestDir>${project.build.directory}/generated-sources/annotations/jaxb</sourceDestDir>
                <packageName>full.package.here</packageName>
            </configuration>
        </execution>
    </executions>

Shafiul
źródło