Comment éviter d'avoir à spécifier l'emplacement WSDL dans un client de service Web généré par CXF ou JAX-WS?

165

Lorsque je génère un client de service Web en utilisant wsdl2java à partir de CXF (qui génère quelque chose de similaire à wsimport), via maven, mes services commencent par des codes comme celui-ci:

@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;
    }

Le chemin absolu codé en dur est vraiment nul. La classe générée ne fonctionnera sur aucun autre ordinateur que le mien.

La première idée est de mettre le fichier WSDL (plus tout ce qu'il importe, les autres WSDL et XSD) quelque part dans un fichier jar et le classpath. Mais nous voulons éviter cela. Puisque tout cela a été généré par CXF et JAXB basés sur les WSDL et XSD, nous ne voyons aucun intérêt à avoir besoin de connaître le WSDL au moment de l'exécution.

L'attribut wsdlLocation est destiné à remplacer l'emplacement WSDL (au moins c'est ce que j'ai lu quelque part), et sa valeur par défaut est "". Puisque nous utilisons maven, nous avons essayé d'inclure <wsdlLocation></wsdlLocation>dans la configuration de CXF pour essayer de forcer le générateur de source à laisser le wsdlLocation vide. Cependant, cela le fait simplement ignorer la balise XML car elle est vide. Nous avons fait un hack vraiment laid et honteux, en utilisant <wsdlLocation>" + "</wsdlLocation>.

Cela change aussi d'autres endroits:

@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;
    }

Donc, mes questions sont:

  1. Avons-nous vraiment besoin d'un emplacement WSDL même si toutes les classes ont été générées par CXF et JAXB? Si oui, pourquoi?

  2. Si nous n'avons pas vraiment besoin de l'emplacement WSDL, quel est le moyen approprié et propre pour empêcher CXF de le générer et de l'éviter complètement?

  3. Quels effets secondaires pourrions-nous avoir avec ce hack? Nous ne pouvons toujours pas tester cela pour voir ce qui se passe, donc si quelqu'un pouvait dire à l'avance, ce serait bien.

Victor Stafusa
la source

Réponses:

206

J'ai finalement trouvé la bonne réponse à cette question aujourd'hui.

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

Notez que j'ai préfixé la valeur wsdlLocationavec classpath:. Cela indique au plugin que le wsdl sera sur le chemin de classe au lieu d'un chemin absolu. Ensuite, il générera un code similaire à celui-ci:

@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;
    }

Notez que cela ne fonctionne qu'avec la version 2.4.1 ou plus récente du cxf-codegen-plugin.

Kyle
la source
8
Lorsque vous utilisez le plug- classpath:in JAX Maven au lieu de CXF, omettez-le dans la <wsdlLocation...ligne.
Twilite
quelqu'un est-il confronté à un problème d'espace de noms avec le code généré par la méthode ci-dessus?
Narendra Jaggi
Devez-vous lister chaque wsdl individuellement si vous en avez plusieurs? Est-il possible d'éviter cela?
pitseeker le
21

Nous utilisons

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

En d'autres termes, utilisez un chemin relatif au chemin de classe.

Je crois que le WSDL peut être nécessaire au moment de l'exécution pour la validation des messages pendant le marshal / unmarshal.

BPS
la source
17

Pour ceux qui utilisent org.jvnet.jax-ws-commons:jaxws-maven-pluginpour générer un client à partir de WSDL au moment de la construction:

  • Placez le WSDL quelque part dans votre src/main/resources
  • Ne préfixez pas le wsdlLocationavecclasspath:
  • Faites préfixer le wsdlLocationavec/

Exemple:

  • WSDL est stocké dans /src/main/resources/foo/bar.wsdl
  • Configurer jaxws-maven-pluginavec <wsdlDirectory>${basedir}/src/main/resources/foo</wsdlDirectory>et<wsdlLocation>/foo/bar.wsdl</wsdlLocation>
Martin Devillers
la source
pourquoi ne pas utiliser le préfixe "wsdlLocation with classpath", je l'utilise et ça marche
Mohammad Sadegh Rafiei
9

1) Dans certains cas, oui. Si le WSDL contient des éléments tels que des stratégies et de ce type qui dirigent le comportement d'exécution, alors le WSDL peut être requis lors de l'exécution. Les artefacts ne sont pas générés pour des éléments liés aux politiques et autres. De plus, dans certains cas obscurs de RPC / Littéral, tous les espaces de noms nécessaires ne sont pas affichés dans le code généré (par spécification). Ainsi, le wsdl leur serait nécessaire. Des cas obscurs cependant.

2) Je pensais que quelque chose comme ça fonctionnerait. Quelle version de CXF? Cela ressemble à un bug. Vous pouvez essayer une chaîne vide ici (juste des espaces). Je ne sais pas si cela fonctionne ou non. Cela dit, dans votre code, vous pouvez utiliser le constructeur qui prend l'URL WSDL et ne passe que null. Le wsdl ne serait pas utilisé.

3) Juste les limitations ci-dessus.

Daniel Kulp
la source
Il s'agit du dernier CXF 2.3.1. Sorti il ​​y a seulement 8 jours. Passer null est une bonne idée, je devrais avoir vu cette réponse évidente avant. Je vais encore essayer les espaces.
Victor Stafusa
Non, les espaces vides font la même chose que rien. ie: la balise XML est complètement ignorée.
Victor Stafusa
5

J'ai pu générer

static {
    WSDL_LOCATION = null;
}

en configurant le fichier pom pour avoir un null pour 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>
raisin
la source
2
Cette solution n'a pas fonctionné pour moi avec CXF 3.1.0. obtenu une erreur org.apache.cxf.tools.common.toolspec.parser.BadUsageException: option inattendue: -wsdlLocation
Chandru
4

Est-il possible que vous puissiez éviter d'utiliser wsdl2java? Vous pouvez immédiatement utiliser les API CXF FrontEnd pour appeler votre Webservice SOAP. Le seul hic, c'est que vous devez créer votre SEI et vos VO du côté de votre client. Voici un exemple de code.

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());

    }
}

Vous pouvez voir le tutoriel complet ici http://weblog4j.com/2012/05/01/developing-soap-web-service-using-apache-cxf/

Niraj Singh
la source
2
Les fichiers WSDL étaient extrêmement compliqués, nous avons donc utilisé la génération automatique pour garantir la compatibilité. L'autogénération a créé des VO et des SEI tout aussi extrêmement compliqués. Nous avons choisi d'utiliser un ensemble distinct d'objets de domaine complètement découplés de ceux générés automatiquement, de sorte que nous n'avons pas interféré avec la génération automatique ni été restreints ou entraînés par elle. Les VO auto-générés n'ont été utilisés que dans le cadre des communications de services et nous les avons gardés aussi éphémères que possible. En d'autres termes, l'une de nos préoccupations est d'éviter d'avoir à coder et gérer manuellement tous les VO.
Victor Stafusa
2
Je suis d'accord avec Victor, car maintenir VO manuellement peut être une perte de temps, et un risque de différences, plus ou moins visibles et nuancées .. c'est exactement le but de wsdl2java, c'est pourquoi c'est utile et sécurisé!
Donatello
4

Mise à jour pour CXF 3.1.7

Dans mon cas, j'ai mis les fichiers WSDL src/main/resourceset ajouté ce chemin à mes Srouces dans Eclipse (clic droit sur Projet-> Chemin de construction -> Configurer le chemin de construction ...-> Source [Tab] -> Ajouter un dossier).

Voici à quoi pomressemble mon fichier et comme on peut le voir, aucune wsdlLocation option n'est nécessaire:

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

Et voici le service généré. Comme on peut le voir, l'URL provient de ClassLoader et non du chemin d'accès absolu au fichier

@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
la source
<configuration> <sourceRoot>${basedir}/src/main/java/</sourceRoot> <wsdlRoot>${basedir}/src/main/resources/</wsdlRoot> <includes> <include>*.wsdl</include> </includes> </configuration> J'inclus tous les fichiers .wsdl dans le chemin de classe, alors comment puis-je spécifier l'emplacement wsdl afin que chaque fichier .java généré puisse contenir le chemin .wsdl respectif? Merci d'avance. @Mazy
Khalid Shah
2

Sérieusement, la meilleure réponse ne fonctionne pas pour moi. essayé cxf.version 2.4.1 et 3.0.10. et générer un chemin absolu avec wsdlLocation à chaque fois.

Ma solution est d'utiliser la wsdl2javacommande dans le apache-cxf-3.0.10\bin\ avec -wsdlLocation classpath:wsdl/QueryService.wsdl.

Détail:

    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
la source
0

La solution @Martin Devillers fonctionne très bien. Pour être complet, fournissez les étapes ci-dessous:

  1. Mettez votre wsdl dans le répertoire de ressources comme: src/main/resource
  2. Dans le fichier pom, ajoutez à la fois wsdlDirectory et wsdlLocation (ne manquez pas / au début de wsdlLocation), comme ci-dessous. Alors que wsdlDirectory est utilisé pour générer du code et wsdlLocation est utilisé lors de l'exécution pour créer un proxy dynamique.

    <wsdlDirectory>src/main/resources/mydir</wsdlDirectory>
    <wsdlLocation>/mydir/my.wsdl</wsdlLocation>
  3. Puis dans votre code java (avec constructeur no-arg):

    MyPort myPort = new MyPortService().getMyPort();
  4. Voici la partie de génération de code complète dans le fichier pom, avec une API fluide dans le code généré.

    <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
la source