¿Cómo evitar la necesidad de especificar la ubicación WSDL en un cliente de servicio web generado CXF o JAX-WS?

165

Cuando genero un cliente de servicio web usando wsdl2java de CXF (que genera algo similar a wsimport), a través de Maven, mis servicios comienzan con códigos como este:

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

El camino absoluto codificado realmente apesta. La clase generada no funcionará en ninguna otra computadora que no sea la mía.

La primera idea es colocar el archivo WSDL (más todo lo que importa, otros WSDL y XSD) en algún lugar en un archivo jar y classpath. Pero queremos evitar esto. Como todo eso fue generado por CXF y JAXB basado en WSDL y XSD, no vemos ningún punto en la necesidad de conocer el WSDL en tiempo de ejecución.

El atributo wsdlLocation está destinado a anular la ubicación WSDL (al menos esto es lo que leí en alguna parte), y su valor predeterminado es "". Como estamos usando maven, intentamos incluir <wsdlLocation></wsdlLocation>dentro de la configuración de CXF para tratar de forzar al generador de origen a dejar wsdlLocation en blanco. Sin embargo, esto simplemente hace que ignore la etiqueta XML porque está vacía. Hicimos un truco vergonzoso realmente feo, usando <wsdlLocation>" + "</wsdlLocation>.

Esto también cambia otros lugares:

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

Entonces, mis preguntas son:

  1. ¿Realmente necesitamos una ubicación WSDL incluso si todas las clases fueron generadas por CXF y JAXB? ¿Si es así por qué?

  2. Si realmente no necesitamos la ubicación WSDL, ¿cuál es la forma correcta y limpia de hacer que CXF no lo genere y lo evite por completo?

  3. ¿Qué efectos secundarios negativos podríamos tener con ese truco? Todavía no podemos probar eso para ver qué sucede, por lo que si alguien pudiera decir con anticipación, sería bueno.

Victor Stafusa
fuente

Respuestas:

206

Finalmente descubrí la respuesta correcta a esta pregunta hoy.

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

Tenga en cuenta que he prefijado el valor wsdlLocationcon classpath:. Esto le dice al complemento que wsdl estará en la ruta de clase en lugar de una ruta absoluta. Entonces generará un código similar a este:

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

Tenga en cuenta que esto solo funciona con la versión 2.4.1 o posterior del plugin cxf-codegen.

Kyle
fuente
8
Cuando utilice el complemento JAX Maven en lugar de CXF, omita classpath:en la <wsdlLocation...línea.
Twilite
¿Alguien se enfrenta a un problema de espacio de nombres con el código generado por el método anterior?
Narendra Jaggi
¿Tiene que enumerar cada wsdl individualmente si tiene varios? ¿Es posible evitar eso?
pitseeker
21

Usamos

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

En otras palabras, use una ruta relativa a la ruta de clase.

Creo que el WSDL puede ser necesario en tiempo de ejecución para la validación de los mensajes durante Mariscal / Unmarshal.

BPS
fuente
17

Para aquellos que usan org.jvnet.jax-ws-commons:jaxws-maven-pluginpara generar un cliente desde WSDL en el momento de la compilación:

  • Coloque el WSDL en algún lugar de su src/main/resources
  • No , no prefijar el wsdlLocationconclasspath:
  • Hacer el prefijo wsdlLocationcon/

Ejemplo:

  • WSDL se almacena en /src/main/resources/foo/bar.wsdl
  • Configurar jaxws-maven-plugincon <wsdlDirectory>${basedir}/src/main/resources/foo</wsdlDirectory>y<wsdlLocation>/foo/bar.wsdl</wsdlLocation>
Martin Devillers
fuente
por qué no usar el prefijo "wsdlLocation with classpath", lo uso y funciona
Mohammad Sadegh Rafiei
9

1) En algunos casos, sí. Si el WSDL contiene cosas como Políticas y tales que dirigen el comportamiento en tiempo de ejecución, entonces el WSDL puede ser requerido en tiempo de ejecución. Los artefactos no se generan para cosas relacionadas con políticas y demás. Además, en algunos casos oscuros de RPC / Literal, no todos los espacios de nombres que se necesitan se generan en el código generado (por especificación). Por lo tanto, el wsdl sería necesario para ellos. Casos oscuros sin embargo.

2) Pensé que algo así funcionaría. ¿Qué versión de CXF? Eso suena como un error. Puede probar una cadena vacía allí (solo espacios). No estoy seguro si eso funciona o no. Dicho esto, en su código, puede usar el constructor que toma la URL WSDL y simplemente pasar nulo. El wsdl no se usaría.

3) Solo las limitaciones anteriores.

Daniel Kulp
fuente
Es el más nuevo CXF 2.3.1. Lanzado hace solo 8 días. Pasar nulo es una buena idea, debería ver esta respuesta obvia antes. Todavía intentaré los espacios.
Victor Stafusa
No, los espacios en blanco hacen lo mismo que nada. es decir: la etiqueta XML se ignora por completo.
Victor Stafusa
5

Pude generar

static {
    WSDL_LOCATION = null;
}

configurando el archivo pom para que tenga un valor nulo para 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
fuente
2
Esta solución no me funcionó con CXF 3.1.0. tiene un error org.apache.cxf.tools.common.toolspec.parser.BadUsageException: opción inesperada: -wsdlLocation
Chandru
4

¿Es posible que pueda evitar usar wsdl2java? Inmediatamente puede usar las API CXF FrontEnd para invocar su servicio web SOAP. El único inconveniente es que necesita crear su SEI y VO en el extremo de su cliente. Aquí hay un código de muestra.

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

    }
}

Puede ver el tutorial completo aquí http://weblog4j.com/2012/05/01/developing-soap-web-service-using-apache-cxf/

Niraj Singh
fuente
2
Los archivos WSDL eran enormemente complicados, por lo que utilizamos la autogeneración como una forma de garantizar la compatibilidad. La autogeneración creó algunos VO y SEI igualmente igualmente complicados. Elegimos usar un conjunto separado de objetos de dominio completamente desacoplados a los autogenerados, por lo que no interferimos con la autogeneración ni fuimos restringidos o impulsados ​​por ella. Los VO autogenerados se usaron solo en el contexto de las comunicaciones de servicios y los mantuvimos lo más cortos posible. En otras palabras, una de nuestras preocupaciones es evitar la necesidad de codificar y administrar manualmente todos los VO.
Victor Stafusa
2
Estoy de acuerdo con Victor, ya que mantener VO manualmente puede ser una pérdida de tiempo y un riesgo de diferencias, más o menos visibles y calificadas ... ese es exactamente el propósito de wsdl2java, ¡por eso es útil y seguro!
Donatello
4

Actualización para CXF 3.1.7

En mi caso, puse los archivos WSDL src/main/resourcesy agregué esta ruta a mis Srouces en Eclipse (haga clic derecho en Proyecto-> Ruta de compilación -> Configurar ruta de compilación ...-> Fuente [pestaña] -> Agregar carpeta).

Así es como se pomve mi archivo y, como se puede ver, NO se wsdlLocation necesita ninguna opción:

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

Y aquí está el Servicio generado. Como se puede ver, la URL se obtiene de ClassLoader y no de Absolute File-Path

@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;   
}
Embrollado
fuente
<configuration> <sourceRoot>${basedir}/src/main/java/</sourceRoot> <wsdlRoot>${basedir}/src/main/resources/</wsdlRoot> <includes> <include>*.wsdl</include> </includes> </configuration> Incluyo todos los archivos .wsdl en la ruta de clase, entonces, ¿cómo puedo especificar la ubicación wsdl para que cada archivo .java generado pueda contener la ruta respectiva .wsdl? Gracias por adelantado. @Mazy
Khalid Shah
2

En serio, la respuesta principal no me funciona. Probé cxf.version 2.4.1 y 3.0.10. y generar ruta absoluta con wsdlLocation cada vez.

Mi solución es usar el wsdl2javacomando en el apache-cxf-3.0.10\bin\ con -wsdlLocation classpath:wsdl/QueryService.wsdl.

Detalle:

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

La solución @Martin Devillers funciona bien. Para completar, proporcionando los pasos a continuación:

  1. Pon tu wsdl en el directorio de recursos como: src/main/resource
  2. En el archivo pom, agregue wsdlDirectory y wsdlLocation (no se pierda / al comienzo de wsdlLocation), como a continuación. Mientras que wsdlDirectory se usa para generar código y wsdlLocation se usa en tiempo de ejecución para crear proxy dinámico.

    <wsdlDirectory>src/main/resources/mydir</wsdlDirectory>
    <wsdlLocation>/mydir/my.wsdl</wsdlLocation>
  3. Luego, en su código Java (sin constructor arg):

    MyPort myPort = new MyPortService().getMyPort();
  4. Aquí está la parte de generación de código completo en el archivo pom, con una API fluida en el código generado.

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