Как избежать необходимости указывать расположение WSDL в сгенерированном клиенте веб-службы CXF или JAX-WS?

165

Когда я генерирую клиент веб-сервиса, используя wsdl2java из CXF (который генерирует что-то похожее на wsimport), через maven, мои сервисы начинаются с таких кодов:

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

Твердо закодированный абсолютный путь действительно отстой. Созданный класс не будет работать ни на каком другом компьютере, кроме моего.

Первая идея состоит в том, чтобы поместить файл WSDL (плюс все, что он импортирует, другие WSDL и XSD) куда-нибудь в jar-файл и найти путь к классам. Но мы хотим избежать этого. Поскольку все это было сгенерировано CXF и JAXB на основе WSDL и XSD, мы не видим смысла в необходимости знать WSDL во время выполнения.

Атрибут wsdlLocation предназначен для переопределения местоположения WSDL (по крайней мере, это то, что я где-то читал), и его значением по умолчанию является "". Поскольку мы используем maven, мы попытались включить <wsdlLocation></wsdlLocation>в конфигурацию CXF, чтобы заставить исходный генератор оставить поле wsdlLocation пустым. Однако это просто заставляет игнорировать тег XML, потому что он пуст. Мы сделали действительно уродливый позорный хак, используя <wsdlLocation>" + "</wsdlLocation>.

Это меняет и другие места:

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

Итак, мои вопросы:

  1. Действительно ли нам нужно расположение WSDL, даже если все классы были сгенерированы CXF и JAXB? Если да, то почему?

  2. Если нам действительно не нужно расположение WSDL, каков правильный и чистый способ заставить CXF не генерировать его и полностью его избегать?

  3. Какие плохие побочные эффекты мы можем получить с этим взломом? Мы до сих пор не можем проверить это, чтобы увидеть, что происходит, поэтому, если кто-то может сказать заранее, было бы хорошо.

Виктор Стафуса
источник

Ответы:

206

Я наконец понял правильный ответ на этот вопрос сегодня.

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

Обратите внимание, что я добавил префикс в значение wsdlLocationс classpath:. Это говорит плагину, что wsdl будет на пути к классам вместо абсолютного пути. Затем он сгенерирует код, подобный следующему:

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

Обратите внимание, что это работает только с версией 2.4.1 или новее cxf-codegen-plugin.

рукав моря
источник
8
При использовании плагина JAX Maven вместо CXF, опустите classpath:в <wsdlLocation...строке.
Twilite
Кто-нибудь сталкивается с проблемой пространства имен с кодом, сгенерированным вышеуказанным методом?
Нарендра Джагги
Нужно ли перечислять каждый wsdl индивидуально, если у вас их несколько? Можно ли этого избежать?
ищущий яна
21

Мы используем

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

Другими словами, используйте путь относительно пути к классам.

Я полагаю, что WSDL может понадобиться во время выполнения для проверки сообщений во время маршала / демаршала.

BPS
источник
17

Для тех, кто использует org.jvnet.jax-ws-commons:jaxws-maven-pluginдля генерации клиента из WSDL во время сборки:

  • Поместите WSDL где-нибудь в вашем src/main/resources
  • Вы не префикс wsdlLocationсclasspath:
  • Сделать префикс wsdlLocationс/

Пример:

  • WSDL хранится в /src/main/resources/foo/bar.wsdl
  • Настроить jaxws-maven-pluginс <wsdlDirectory>${basedir}/src/main/resources/foo</wsdlDirectory>и<wsdlLocation>/foo/bar.wsdl</wsdlLocation>
Мартин Девиллерс
источник
почему не использовать префикс «wsdlLocation с classpath», я использую его, и он работает
Мохаммад Садег Рафией
9

1) В некоторых случаях да. Если WSDL содержит такие вещи, как политики и тому подобное, которые управляют поведением во время выполнения, тогда WSDL может потребоваться во время выполнения. Артефакты не создаются для связанных с политикой вещей и тому подобного. Кроме того, в некоторых непонятных случаях RPC / Literal не все необходимые пространства имен выводятся в сгенерированном коде (согласно спецификации). Таким образом, WSDL будет необходимо для них. Темные случаи, хотя.

2) Я думал, что-то вроде будет работать. Какая версия CXF? Это звучит как ошибка. Вы можете попробовать пустую строку там (только пробелы). Не уверен, работает ли это или нет. Тем не менее, в вашем коде вы можете использовать конструктор, который принимает URL WSDL и просто передает null. WSDL не будет использоваться.

3) Только ограничения выше.

Дэниел Кулп
источник
Это новейший CXF 2.3.1. Выпущено всего 8 дней назад. Передача нуля - хорошая идея, я должен был увидеть этот очевидный ответ раньше. Я все еще попробую пробелы.
Виктор Стафуса
Нет, пробелы делают то же самое, что и ничего. т.е. тег XML полностью игнорируется.
Виктор Стафуса
5

Я был в состоянии генерировать

static {
    WSDL_LOCATION = null;
}

путем настройки pom-файла для нулевого значения для 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
источник
2
Это решение не работает для меня с CXF 3.1.0. произошла ошибка org.apache.cxf.tools.common.toolspec.parser.BadUsageException: неожиданная опция: -wsdlLocation
Chandru
4

Возможно ли, что вы можете избежать использования wsdl2java? Вы можете сразу использовать API-интерфейсы CXF FrontEnd для вызова вашего веб-сервиса SOAP. Единственный улов заключается в том, что вам нужно создать свои SEI и VO на стороне клиента. Вот пример кода.

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

    }
}

Вы можете увидеть полное руководство здесь http://weblog4j.com/2012/05/01/developing-soap-web-service-using-apache-cxf/

Нирадж Сингх
источник
2
Файлы WSDL были чрезвычайно сложными, поэтому мы использовали автогенерацию как способ обеспечения совместимости. Автогенерация создала несколько одинаково сложных ВО и SEI. Мы решили использовать отдельный набор доменных объектов, полностью отделенных от автоматически сгенерированных, поэтому мы не вмешивались в автогенерацию и не ограничивались и не управлялись ею. Автоматически сгенерированные VO использовались только в контексте коммуникаций служб, и мы сохранили их как можно более короткими. Другими словами, одна из наших задач - избежать необходимости вручную кодировать и управлять всеми VO.
Виктор Стафуса
2
Я согласен с Виктором, так как поддержание VO вручную может быть пустой тратой времени и риском различий, более или менее видимых и квалифицированных ... именно в этом и заключается цель wsdl2java, поэтому он полезен и безопасен!
Донателло
4

Обновление для CXF 3.1.7

В моем случае я поместил файлы WSDL src/main/resourcesи добавил этот путь к своим источникам в Eclipse (щелкните правой кнопкой мыши Project-> Build Path -> Configure Build Path ...-> Source [Tab] -> Add Folder).

Вот как pomвыглядит мой файл, и, как видно, никакой wsdlLocation опции не требуется:

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

А вот и созданный Сервис. Как видно, URL получен из ClassLoader, а не из Абсолютного пути к файлу.

@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;   
}
запутанный
источник
<configuration> <sourceRoot>${basedir}/src/main/java/</sourceRoot> <wsdlRoot>${basedir}/src/main/resources/</wsdlRoot> <includes> <include>*.wsdl</include> </includes> </configuration> Я включаю все файлы .wsdl в путь к классу, тогда как я могу указать расположение wsdl, чтобы каждый сгенерированный файл .java содержал соответствующий путь .wsdl? Заранее спасибо. @Mazy
Халид Шах
2

Серьезно, главный ответ не работает для меня. пробовал cxf.version 2.4.1 и 3.0.10. и каждый раз генерировать абсолютный путь с помощью wsdlLocation.

Мое решение состоит в том, чтобы использовать wsdl2javaкоманду в apache-cxf-3.0.10\bin\ с -wsdlLocation classpath:wsdl/QueryService.wsdl.

Деталь:

    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
источник
0

Решение @Martin Devillers работает отлично. Для полноты, предоставив следующие шаги:

  1. Поместите ваш wsdl в каталог ресурсов, например: src/main/resource
  2. В файле pom добавьте как wsdlDirectory, так и wsdlLocation (не пропустите / в начале wsdlLocation), как показано ниже. В то время как wsdlDirectory используется для генерации кода, а wsdlLocation используется во время выполнения для создания динамического прокси.

    <wsdlDirectory>src/main/resources/mydir</wsdlDirectory>
    <wsdlLocation>/mydir/my.wsdl</wsdlLocation>
  3. Затем в вашем коде Java (с конструктором без аргументов):

    MyPort myPort = new MyPortService().getMyPort();
  4. Вот полная часть генерации кода в pom-файле с текущими API в сгенерированном коде.

    <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
источник