JAX-WS Загрузка WSDL из банки

Я пишу толстый клиент, который использует SOAP-сервис для некоторых функций (сообщения об ошибках и т.д.).

У меня JAX-WS работает нормально, но по умолчанию (по крайней мере в netbeans) он извлекает WSDL с удаленного сервера каждый раз, когда служба инициализируется. Я ожидаю, что это поможет обеспечить поддержку версий и т.д., Но это не то, что я хочу.

Я добавил аргумент wsdllocation в wsimport, чтобы указать сгенерированные классы на локальный ресурс. Следующий фрагмент - это загрузка URL-адреса для ресурса WSDL из ApplicationService.java.

baseUrl = net.example.ApplicationService.class.getResource(".");
url = new URL(baseUrl, "service.wsdl");

Я уверен, что у него не должно быть проблем с указанием ресурса, хранящегося в банке в пакете net/example/resources, и сама банка построена так, как ожидалось. Однако служба не будет загружаться... в частности, я получаю исключение NullPointerException при вызове ApplicationService.getPort();

Возможно ли это? или просто дикая гусиная погоня?

Ответ 1

Да, это определенно возможно, поскольку я сделал это при создании клиентов через javax.xml.ws.EndpointReference, класс, связанный с WS-A. Я добавил ссылку на classpath на WSDL на WS-A EndPointReference, а реализация Metro JAX-WS загрузила его просто отлично. Независимо от того, загружаете WSDL из WS-A EndPointReference или из URL-адреса файла или http, ваша реализация JAX-WS должна использовать тот же код разбора WSDL, что и все, что вы делаете, это разрешать URL-адреса.

Лучший подход для вас - это, вероятно, сделать что-то вроде следующего:

URL wsdlUrl = MyClass.class.getResource(
            "/class/path/to/wsdl/yourWSDL.wsdl");

Service yourService= Service.create(
            wsdlUrl,
            ...);

Где... представляет QName службы WSDL внутри вашего WSDL. Теперь важно помнить, что ваш WSDL должен быть полным и действительным. Это означает, что если ваш WSDL импортирует XSD файлы или другие WSDL файлы, URL-адреса должны быть правильными. Если вы включили импортированные WSDL и XSD в том же JAR, что и файл WSDL, вы должны использовать относительные URL-адреса для импорта и сохранить все свои импортные товары в одном JAR файле. Обработчик URL JAR не рассматривает относительные URL как относительные относительно пути к классам, а скорее относительные в JAR файле, поэтому вы не можете импортировать в WSDL, который выполняется через JAR, если вы не реализуете собственный обработчик URL и свой собственный префикс classpath - разрешение импорта. Если ваш WSDL импортирует внешние ресурсы, это нормально, но вы подписываетесь на проблемы обслуживания, если эти ресурсы когда-либо перемещаются. Даже использование статической копии WSDL из вашего пути к классам противоречит духу WSDL, веб-сервисов и JAX-WS, но есть моменты, когда это необходимо.

Наконец, если вы вставляете статический WSDL, я предлагаю вам как минимум настроить конечную точку службы для тестирования и развертывания. Код для перенастройки конечной точки вашего клиента веб-службы выглядит следующим образом:

  YourClientInterface client = yourService.getPort(
            new QName("...", "..."),
            YourClientInterface.class);
  BindingProvider bp = (BindingProvider) client;
  bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
                "http://localhost:8080/yourServiceEndpoint");

Ответ 2

По крайней мере, для недавнего JAX-WS вам не нужно делать какие-либо каталоги схем или программные настройки местоположения wsdl IF, вы помещаете WSDL в JAR, а затем устанавливаете wsimport wsdlLocation относительной путь ресурса WSDL в JAR. Это JAX-WS использует Java builtin Class.getResource для загрузки WSDL.

Если вы используете Maven, это что-то вроде:

  <plugin>
    <groupId>org.jvnet.jax-ws-commons</groupId>
    <artifactId>jaxws-maven-plugin</artifactId>
    <version>2.3</version>
    <executions>
      <execution>
        <goals>
          <goal>wsimport</goal>
        </goals>
        <!-- Following configuration will invoke wsimport once for each wsdl. -->
        <configuration>
            <!--- VERY IMPORTANT THAT THE PATH START WITH '/' -->
    <wsdlLocation>/com/adamgent/ws/blah.wsdl</wsdlLocation>
    <wsdlDirectory>${basedir}/src/main/resources/com/adamgent/ws</wsdlDirectory>
    <wsdlFiles><wsdlFile>blah.wsdl</wsdlFile></wsdlFiles>
       </configuration>
      </execution>
    </executions>
  </plugin>

В приведенном выше примере вы, таким образом, разместите WSDL, используя макет проекта Maven здесь src/main/resources/com/adamgent/ws.

Убедитесь, что WSDL попадает в JAR для Maven, например:

<build>
      <resources>
        <resource>
          <directory>src/main/resources</directory>
        </resource>
      </resources> ....

Теперь ваш созданный wsimport код и WSDL находятся в автономном JAR. Чтобы использовать эту услугу, вам не нужно устанавливать местоположение WSDL и так же просто:

BlahService myService = new BlayService_Service().getBlahServicePort();

Это должно быть тривиально отображать это на ANT wsimport.

Ответ 3

Возможно, немного поздно, но я нашел довольно простое решение, которое помогло решить эту проблему, но это связано с изменением сгенерированного кода класса Service:

Если следующая строка в классе службы

baseUrl = net.example.ApplicationService.class.getResource(".");

изменяется на

baseUrl = net.example.ApplicationService.class.getResource("");

он отлично работает даже с WSDL, который упакован в JAR. Не уверен в точном предполагаемом поведении getResource() в любом из этих случаев, но до сих пор у меня не возникало никаких проблем с этим подходом на нескольких версиях ОС и Java.

Ответ 5

Если ваш classpath имеет "." в этом случае Class.getResource( "." ) вернет URL-адрес каталога, из которого вы выполнили команду java. В противном случае он вернет нуль. Соответственно отрегулируйте wsdllocation.

Ответ 6

Другой ответ: Uste новая служба (wsdllocation, servicename); для получения Сервисного объекта.

Вот как я решил проблему.

Ответ 7

Я наткнулся на ту же проблему. JAXWS генерирует клиентский код, используя трюк MyService.class.getResource(".") для загрузки файла wsdl... но после тестирования это, похоже, работает только в том случае, если файл класса находится в каталоге на файловом массиве. Если файл класса находится в JAR, этот вызов возвращает null для URL.

Это звучит как ошибка в JDK, поскольку, если вы создаете свой URL-адрес следующим образом:

final URL url = new URL( MyService.class.getResource( MyService.class.getSimpleName() + ".class"), "myservice.wsdl");

то он также работает, если класс и wsdl связаны в банке.

Я думаю, что большинство людей на самом деле собираются в банке!

Ответ 8

Я заменил место WSDL, прежде чем создавать клиентскую банку здесь.

  • Скопируйте WSDL в каталог dir.
  • Заменить класс службы ссылкой на WSDL с помощью пути к классам.
  • создать клиентские заглушки.
  • запишите заглушки.
<copy todir="@{dest-dir}/@{dir-package}" verbose="@{verbose}">
  <fileset dir="@{dir-wsdl}" includes="*.wsdl,*.xsd" />
</copy>
<echo message="Replacing Service to point to correct WSDL path..." />
<replaceregexp match="new URL(.*)" replace='Class.class.getResource("@{name-wsdl}");' flags="gm">
  <fileset dir="@{source-dest-dir}">
    <include name="@{dir-package}/*Service.java" />
  </fileset>
</replaceregexp>
<replaceregexp match="catch (.*)" replace='catch (Exception ex) {' flags="gm">
  <fileset dir="@{source-dest-dir}">
    <include name="@{dir-package}/*Service.java" />
  </fileset>
</replaceregexp>

Ответ 9

Здесь мой hack-y обходной путь.

Я распакую WSDL из jar и записываю его в файл рядом с банкой:

File wsdl = new File("../lib/service.wsdl");
InputStream source = getClass().getResource("resources/service.wsdl").openStream();
FileOutputStream out = new FileOutputStream(wsdl);

byte[] buffer = new byte[512];
int read;
while((read = source.read(buffer)) >= 0) {
    out.write(buffer, 0, read);
}

Затем укажите классы обслуживания на file:../lib/service.wsdl.

Это работает, но я был бы признателен, если кто-нибудь сможет показать мне более элегантное решение.

Ответ 10

Вот один из них, который работает для меня (в частности, через http и https). Случай JAX-WS Oracle JDK 1.8.0_51, работающий с классами, созданными Apache CXF 3.1.1.

Обратите внимание, что удаленный WSDL получается только при первом вызове в любом случае. В зависимости от шаблона использования (длительная программа) это может быть вполне приемлемым.

Основы:

  • Загрузите WSDL с удаленного хоста и сохраните его как файл: wget --output-document=wsdl_raw.xml $WSDL_URL
  • Для хорошего форматирования вы можете xmllint --format wsdl_raw.xml > wsdl.xml
  • Создайте классы клиентов, используя инструмент командной строки: ./cxf/bin/wsdl2java -d output/ -client -validate wsdl.xml и импортируйте в свой проект

Убедитесь, что в WSDL файле существуют определения служб для http и https. В моем случае у провайдера не было одного для https (но он принимал https-трафик), и мне пришлось добавить его вручную. Что-то вроде этих строк должно быть в WSDL:

  <wsdl:service name="fooservice">
    <wsdl:port binding="tns:fooserviceSoapBinding" name="FooBarWebServicePort">
      <soap:address location="http://ws.example.com/a/b/FooBarWebService"/>
    </wsdl:port>
  </wsdl:service>
  <wsdl:service name="fooservice-secured">
    <wsdl:port binding="tns:fooserviceSoapBinding" name="FooBarWebServicePort">
      <soap:address location="https://ws.example.com/a/b/FooBarWebService"/>
    </wsdl:port>
  </wsdl:service>

CXF должен был сгенерировать класс, который реализует javax.xml.ws.Service, вызываемый, например, Fooservice, с соответствующими конструкторами:

public class Fooservice extends Service {

  public Fooservice(URL wsdlLocation) {
      super(wsdlLocation, SERVICE);
  }

  public Fooservice(URL wsdlLocation, QName serviceName) {
      super(wsdlLocation, serviceName);
  }

  public Fooservice() {
      super(WSDL_LOCATION, SERVICE);
  }

  ...etc...

Где-то в вашем коде (здесь, некоторые Groovy для упрощения чтения), вы инициализируете вышеуказанный экземпляр Service, а затем вызываете порт. Здесь, в зависимости от флага под названием secure, мы используем https или http:

static final String NAMESPACE = 'com.example.ws.a.b'
static final QName SERVICE_NAME_HTTP = new QName(NAMESPACE, 'fooservice')
static final QName SERVICE_NAME_HTTPS = new QName(NAMESPACE, 'fooservice-secured')

Fooservice wsService
File wsdlfile = new File('/somewhere/on/disk/wsdl.xml')

// If the file is missing there will be an exception at connect
// time from sun.net.www.protocol.file.FileURLConnection.connect
// It should be possible to denote a resource on the classpath 
// instead of a file-on-disk. Not sure how, maybe by adding a handler
// for a 'resource:' URL scheme?

URI wsdlLocationUri = java.nio.file.Paths(wsdlfile.getCanonicalPath()).toUri()

if (secure) {
  wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTPS)
}
else {
  wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTP)
}

SomeServicePort port = wsService.getSomeServicePort()

port.doStuff()

Альтернатива, которая загружает WSDL в соединение, которое отделено от соединения, используемого для вызова службы (используйте tcpdump -n -nn -s0 -A -i eth0 'tcp port 80' для наблюдения за трафиком), просто:

URI wsdlLocationUri

if (secure) {
   wsdlLocationUri = new URI('https://ws.example.com/a/b/FooBarWebService?wsdl')
}
else {
   wsdlLocationUri = new URI('http://ws.example.com/a/b/FooBarWebService?wsdl')
}

Fooservice wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTP)

SomeServicePort port = wsService.getSomeServicePort()

port.doStuff()

Обратите внимание, что это действительно правильно использует https, если wsdlLocationUri указывает https, несмотря на то, что wsService был инициализирован с помощью SERVICE_NAME_HTTP. (Не знаете, почему - использует ли служба схему, используемую для извлечения ресурса WSDL?)

И что об этом.

Для отладки подключения выполните:

-Dcom.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump=true
-Dcom.sun.xml.internal.ws.transport.http.HttpAdapter.dump=true

для JVM в командной строке. Это будет записывать информацию из кода http-соединения в stdout (к сожалению, НЕ к java.util.logging. Oracle, пожалуйста!).

Ответ 11

Мое решение состояло в том, чтобы изменить созданный Сервис. Вы должны изменить wsdlLocation в аннотации заголовка, а блок создания объектов выглядит следующим образом:

    static {
    URL url = null;
    url = com.ups.wsdl.xoltws.ship.v1.ShipService.class.getResource("Ship.wsdl");
    SHIPSERVICE_WSDL_LOCATION = url;
    }

Я помещаю wsdl файл в каталог bin рядом с классом ShipService

Ответ 12

Хотя вы можете заставить его работать с некоторыми манипуляциями, я бы порекомендовал не делать этого и держать его так, как вы сейчас.

Поставщики конечных точек веб-службы должны предоставлять WSDL в рамках своего контракта. Создаваемый вами код должен вытаскиваться из WSDL с самого сервера.

При развертывании в WebSphere вы можете изменить конечные точки на другие конечные точки из интерфейса развертывания. Другие серверы приложений, которые вам могут понадобиться, чтобы определить специфический для поставщика XML-документ, чтобы сделать это.

Это происходит только при инициализации, поэтому воздействие на ваше общее приложение должно быть незначительным.