Como evitar a necessidade de especificar o local WSDL em um cliente de serviço da web gerado por CXF ou JAX-WS?

165

Quando eu gero um cliente de serviço da web usando wsdl2java do CXF (que gera algo semelhante ao wsimport), via maven, meus serviços começam com 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;
    }

O caminho absoluto codificado realmente é péssimo. A classe gerada não funcionará em nenhum outro computador além do meu.

A primeira idéia é colocar o arquivo WSDL (mais tudo o que ele importa, outros WSDLs e XSDs) em algum lugar do arquivo jar e classpath. Mas queremos evitar isso. Como tudo isso foi gerado pelo CXF e JAXB com base nos WSDLs e XSDs, não vemos sentido em precisar conhecer o WSDL em tempo de execução.

O atributo wsdlLocation destina-se a substituir o local WSDL (pelo menos é o que eu li em algum lugar) e seu valor padrão é "". Como estamos usando o maven, tentamos incluir <wsdlLocation></wsdlLocation>dentro da configuração do CXF para tentar forçar o gerador de origem a deixar o wsdlLocation em branco. No entanto, isso simplesmente ignora a tag XML porque está vazia. Nós fizemos um truque vergonhoso, realmente feio, usando <wsdlLocation>" + "</wsdlLocation>.

Isso muda outros lugares também:

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

Então, minhas perguntas são:

  1. Realmente precisamos de um local WSDL, mesmo que todas as classes tenham sido geradas por CXF e JAXB? Se sim, por que?

  2. Se realmente não precisamos da localização do WSDL, qual é a maneira correta e limpa de fazer com que o CXF não a gere e evite totalmente?

  3. Que efeitos colaterais ruins podemos obter com esse hack? Ainda não podemos testar isso para ver o que acontece, por isso, se alguém pudesse dizer com antecedência, seria bom.

Victor Stafusa
fonte

Respostas:

206

Finalmente, descobri a resposta certa para essa pergunta hoje.

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

Observe que eu prefixei o valor wsdlLocationcom classpath:. Isso informa ao plugin que o wsdl estará no caminho de classe em vez de um caminho absoluto. Em seguida, ele irá gerar um código semelhante 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;
    }

Observe que isso funciona apenas com a versão 2.4.1 ou mais recente do cxf-codegen-plugin.

Kyle
fonte
8
Ao usar o JAX Maven Plugin em vez de CXF, omita classpath:na <wsdlLocation...linha.
Twilite
alguém está enfrentando um problema no namespace com o código gerado pelo método acima?
Narendra Jaggi
Você precisa listar cada wsdl individualmente se tiver vários? É possível evitar isso?
pitseeker 23/01
21

Nós usamos

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

Em outras palavras, use um caminho relativo ao caminho de classe.

Acredito que o WSDL pode ser necessário em tempo de execução para validação de mensagens durante o empate / desempacote.

BPS
fonte
17

Para aqueles que usam org.jvnet.jax-ws-commons:jaxws-maven-pluginpara gerar um cliente a partir do WSDL no momento da construção:

  • Coloque o WSDL em algum lugar do seu src/main/resources
  • Você não prefixar o wsdlLocationcomclasspath:
  • Prefixe o wsdlLocationcom/

Exemplo:

  • WSDL é armazenado em /src/main/resources/foo/bar.wsdl
  • Configure jaxws-maven-plugincom <wsdlDirectory>${basedir}/src/main/resources/foo</wsdlDirectory>e<wsdlLocation>/foo/bar.wsdl</wsdlLocation>
Martin Devillers
fonte
por que não usar o prefixo "wsdlLocation with classpath", eu o uso e funciona
Mohammad Sadegh Rafiei
9

1) Em alguns casos, sim. Se o WSDL contiver itens como Políticas e direcionar o comportamento do tempo de execução, o WSDL poderá ser necessário no tempo de execução. Os artefatos não são gerados para itens relacionados à política e afins. Além disso, em alguns casos obscuros de RPC / Literal, nem todos os namespaces necessários são impressos no código gerado (por especificação). Assim, o wsdl seria necessário para eles. Casos obscuros.

2) Eu pensei que algo assim iria funcionar. Qual versão do CXF? Isso soa como um bug. Você pode tentar uma string vazia (apenas espaços). Não tenho certeza se isso funciona ou não. Dito isto, em seu código, você pode usar o construtor que pega a URL do WSDL e apenas passa nulo. O wsdl não seria usado.

3) Apenas as limitações acima.

Daniel Kulp
fonte
É o mais novo CXF 2.3.1. Lançado há apenas 8 dias. Passar nulo é uma boa ideia, eu deveria ter visto essa resposta óbvia antes. Ainda vou tentar os espaços.
Victor Stafusa
Não, espaços em branco fazem o mesmo que nada. ou seja: a tag XML é completamente ignorada.
22410 Victor Stafusa
5

Eu fui capaz de gerar

static {
    WSDL_LOCATION = null;
}

configurando o arquivo pom para ter um 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
fonte
2
Esta solução não funcionou para mim com o CXF 3.1.0. obteve um erro org.apache.cxf.tools.common.toolspec.parser.BadUsageException: Opção inesperada: -wsdlLocation #
Chandru
4

É possível que você evite usar o wsdl2java? Você pode usar imediatamente as APIs do CXF FrontEnd para chamar seu serviço da Web SOAP. O único problema é que você precisa criar seu SEI e VOs no seu cliente. Aqui está um código de exemplo.

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

    }
}

Você pode ver o tutorial completo aqui http://weblog4j.com/2012/05/01/developing-soap-web-service-using-apache-cxf/

Niraj Singh
fonte
2
Os arquivos WSDL eram extremamente complicados; portanto, usamos a geração automática como uma maneira de garantir a compatibilidade. A geração automática criou alguns VOs e SEIs igualmente extremamente complicados. Optamos por usar um conjunto separado de objetos de domínio completamente dissociados dos gerados automaticamente, portanto, não interferimos na geração automática nem fomos restringidos ou controlados por ela. Os VOs gerados automaticamente foram usados ​​apenas no contexto de comunicações de serviços e os mantivemos o mais breve possível. Em outras palavras, uma de nossas preocupações é evitar a necessidade de codificar e gerenciar manualmente todos os VOs.
precisa saber é o seguinte
2
Eu concordo com Victor, pois manter o VO manualmente pode ser uma perda de tempo e um risco de diferenças, mais ou menos visíveis e qualificadas .. esse é exatamente o objetivo do wsdl2java, é por isso que é útil e seguro!
Donatello
4

Atualização para CXF 3.1.7

No meu caso, eu coloquei os arquivos WSDL src/main/resourcese adicionei esse caminho aos meus Srouces no Eclipse (clique com o botão direito do mouse em Projeto-> Caminho da construção -> Configurar caminho da construção ...-> [Guia] -> Adicionar pasta da fonte).

Aqui está a aparência do meu pomarquivo e, como pode ser visto, NÃO há wsdlLocation necessidade de opção:

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

E aqui está o serviço gerado. Como pode ser visto, o URL é obtido do ClassLoader e não do caminho absoluto do arquivo

@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
fonte
<configuration> <sourceRoot>${basedir}/src/main/java/</sourceRoot> <wsdlRoot>${basedir}/src/main/resources/</wsdlRoot> <includes> <include>*.wsdl</include> </includes> </configuration> Eu incluo todos os arquivos .wsdl no caminho da classe. Como posso especificar o local wsdl para que todos os arquivos .java gerados contenham o respectivo caminho .wsdl? Desde já, obrigado. @Mazy
Khalid Shah
2

Sério, a melhor resposta não está funcionando para mim. tentei cxf.version 2.4.1 e 3.0.10. e gere um caminho absoluto com wsdlLocation todas as vezes.

Minha solução é usar o wsdl2javacomando no apache-cxf-3.0.10\bin\ com -wsdlLocation classpath:wsdl/QueryService.wsdl.

Detalhe:

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

A solução @Martin Devillers funciona bem. Para garantir a integridade, forneça as etapas abaixo:

  1. Coloque seu wsdl no diretório de recursos como: src/main/resource
  2. No arquivo pom, adicione wsdlDirectory e wsdlLocation (não perca / no início de wsdlLocation), como abaixo. Enquanto wsdlDirectory é usado para gerar código e wsdlLocation é usado em tempo de execução para criar proxy dinâmico.

    <wsdlDirectory>src/main/resources/mydir</wsdlDirectory>
    <wsdlLocation>/mydir/my.wsdl</wsdlLocation>
  3. Em seguida, no seu código java (com construtor no-arg):

    MyPort myPort = new MyPortService().getMyPort();
  4. Aqui está a parte completa da geração de código no arquivo pom, com API fluente no código gerado.

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