it-swarm.com.de

JAX-WS Laden der WSDL aus dem Jar

Ich schreibe einen dicken Client, der einen SOAP - Dienst für einige Funktionen verwendet (Fehlerberichterstattung usw.).

JAX-WS funktioniert einwandfrei, aber standardmäßig (zumindest in Netbeans) wird die WSDL bei jeder Initialisierung des Dienstes vom Remote-Server abgerufen. Ich gehe davon aus, dass dies zur Unterstützung der Versionsverwaltung usw. beiträgt, aber es ist nicht das, was ich will.

Ich habe wsdllocation arg zu wsimport hinzugefügt, um die generierten Klassen auf eine lokale Ressource zu verweisen. Das folgende Snippet enthält die URL, die für die WSDL-Ressource aus ApplicationService.Java geladen wird.

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

Ich bin mir ziemlich sicher, dass es keine Probleme geben sollte, auf eine Ressource zu verweisen, die in einem Jar im net/example/resources-Paket gespeichert ist, und der Jar selbst ist wie erwartet aufgebaut. Der Dienst wird jedoch nicht geladen ... Insbesondere bekomme ich eine NullPointerException, wenn ich ApplicationService.getPort () aufrufe.

Ist das möglich? oder nur eine wilde Gansjagd?

41
Cogsy

Ja, dies ist auf jeden Fall möglich, da ich dies beim Erstellen von Clients über javax.xml.ws.EndpointReference, einer WS-A-Klasse, durchgeführt habe. Ich habe der WS-A-EndPointReference einen Klassenpfad-Verweis auf die WSDL hinzugefügt, und die Metro-Implementierung von JAX-WS hat sie gut geladen. Unabhängig davon, ob Sie die WSDL von der WS-A EndPointReference oder von einer Datei oder einer http-URL laden, sollte Ihre JAX-WS-Implementierung denselben WSDL-Analysecode verwenden, während Sie nur URLs auflösen.

Der beste Ansatz für Sie ist wahrscheinlich Folgendes zu tun:

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

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

Where ... steht für den QName eines WSDL-Services in Ihrer WSDL. Wichtig ist zu beachten, dass Ihre WSDL vollständig und gültig sein muss. Das heißt, wenn Ihre WSDL XSD-Dateien oder andere WSDLs importiert, müssen die URLs korrekt sein. Wenn Sie Ihre importierten WSDL- und XSD-Dateien in derselben JAR-Datei wie die WSDL-Datei enthalten, sollten Sie für den Import relative URLs verwenden und alle Importe in derselben JAR-Datei behalten. Der JAR-URL-Handler behandelt die relativen URLs nicht als relativ in Bezug auf den Klassenpfad, sondern relativ in der JAR-Datei, sodass in der WSDL keine Importe über JARs möglich sind, es sei denn, Sie implementieren einen benutzerdefinierten URL-Handler und ein eigenes Präfix klassenpfadbasierte Lösung der Importe. Wenn Ihre WSDL externe Ressourcen importiert, ist dies in Ordnung, Sie melden sich jedoch für Wartungsprobleme an, falls sich diese Ressourcen verschieben. Sogar die Verwendung einer statischen Kopie der WSDL aus Ihrem Klassenpfad widerspricht dem Geist von WSDL, Webservices und JAX-WS. In manchen Fällen ist dies jedoch erforderlich.

Wenn Sie eine statische WSDL einbetten, schlage ich vor, den Serviceendpunkt für Test- und Bereitstellungszwecke zumindest konfigurierbar zu machen. Der Code zum Neukonfigurieren des Endpunkts Ihres Web Service Client lautet wie folgt:

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

Bei der letzten JAX-WS müssen Sie keine Schemakataloge oder die programmgesteuerte Wsdl-Speicherorteinstellung AUSFÜHREN, WENN Sie die WSDL in die JAR einfügen und dann wsimport wsdlLocation auf den relativen Ressourcenpfad der WSDL setzen das Gefäss. Das heißt, JAX-WS verwendet Javas eingebauten Class.getResource, um die WSDL zu laden.

Wenn Sie mit Maven arbeiten, ist es ungefähr so:

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

Für das obige Beispiel würden Sie also die WSDL unter Verwendung des Maven-Projektlayouts hier src/main/resources/com/adamgent/ws einfügen.

Stellen Sie sicher, dass die WSDL wie folgt in die JAR für Maven gerät:

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

Ihr von wsimport generierter Code und Ihre WSDL befinden sich jetzt in einem in sich geschlossenen JAR. Um den Dienst verwenden zu können, müssen Sie den WSDL-Speicherort nicht festlegen.

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

Es sollte trivial sein, dies dem Wsimport von ANT zuzuordnen.

14
Adam Gent

Vielleicht etwas spät, aber ich habe eine recht einfache Lösung gefunden, mit der dieses Problem gelöst werden konnte. Dies führte jedoch zu einer Änderung des generierten Codes der Service-Klasse:

Wenn die folgende Zeile in der Service-Klasse

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

wird in geändert

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

es funktioniert auch mit einer WSDL, die in einem JAR gepackt ist. Ich bin mir in beiden Fällen nicht sicher über das genaue vermeintliche Verhalten von getResource (), aber bisher sind bei diesem Ansatz keine Probleme aufgetreten, unter verschiedenen Betriebssystemen und Java-Versionen.

6
weiresr

Was Sie beschreiben, ist ein Fehler in JAX-WS: JAX_WS-888 - Falscher Code zum Auflösen der URL für eine benutzerdefinierte wsdlLocation .

Es wurde für V2.2 behoben, so dass jetzt nur das Setzen von wsdlLocation beim Schreiben funktionieren sollte.

5
sleske

Eine andere Antwort ist die Verwendung der 

new Service(wsdllocation, servicename );

um das Service-Objekt zu erhalten. 

So habe ich das Problem gelöst.

3
thorty

Wenn Ihr Klassenpfad "." Dann gibt Class.getResource (".") die URL des Verzeichnisses zurück, von dem aus Sie den Java-Befehl ausgeführt haben. Andernfalls wird eine Null zurückgegeben. Passen Sie die WSD-Position entsprechend an.

3
nagarjun

Ich habe die WSDL-Position ersetzt, bevor ich das Client-Glas hier baue.

  1. Kopieren Sie die WSDLs in Klassenverzeichnis.
  2. Ersetzen Sie die Service-Klasse. Verweisen Sie auf WSDL mithilfe von Classpath.
  3. erstellen Sie die Client-Stubs.
  4. die Stummel rütteln.
<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>
2
kiri

Ich bin über das gleiche Problem gestolpert. Der JAXWS-Clientcode zum Generieren verwendet den MyService.class.getResource(".")-Trick, um die wsdl-Datei zu laden. Nach dem Testen scheint dies jedoch nur zu funktionieren, wenn sich die Klassendatei in einem Verzeichnis im Dateisystem befindet. Wenn sich die Klassendatei in einer JAR-Datei befindet, gibt dieser Aufruf den Wert null zurück. 

Es klingt wie ein Fehler im JDK, wenn Sie Ihre URL folgendermaßen erstellen:

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

dann funktioniert es auch, wenn die Klasse und das Wsdl in einem Jar gebündelt sind.

Ich denke, die meisten Leute werden sich tatsächlich in einem Glas bündeln!

2
David Nouls

Hier ist meine hacky-Abhilfe.

Ich entpacke die WSDL aus dem Glas und schreibe sie in eine Datei in der Nähe des Glas:

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

Verweisen Sie dann die Serviceklassen auf file:../lib/service.wsdl.

Das funktioniert, aber ich würde es begrüßen, wenn mir jemand eine elegantere Lösung zeigen könnte. 

1
Cogsy

Hier ist eine, die für mich funktioniert (insbesondere über http und https). Fall von JAX-WS von Oracle JDK 1.8.0_51, das mit Klassen arbeitet, die von Apache CXF 3.1.1 erstellt wurden.

Beachten Sie, dass die entfernte WSDL in jedem Fall nur beim ersten Aufruf abgerufen wird. Abhängig vom Nutzungsmuster (lang laufendes Programm) kann dies durchaus akzeptabel sein.

Die Grundlagen:

  • Laden Sie die WSDL vom Remote-Host herunter und speichern Sie sie als Datei: wget --output-document=wsdl_raw.xml $WSDL_URL
  • Möglicherweise möchten Sie xmllint --format wsdl_raw.xml > wsdl.xml für die Nice-Formatierung
  • Generieren Sie Client-Klassen mit dem Befehlszeilentool : ./cxf/bin/wsdl2Java -d output/ -client -validate wsdl.xml und importieren Sie sie in Ihr Projekt

Stellen Sie sicher, dass Service-Definitionen für http und https in der WSDL-Datei vorhanden sind. In meinem Fall hatte der Anbieter keinen für https (akzeptierte jedoch https traffic), und ich musste ihn manuell hinzufügen. Etwas in dieser Richtung sollte in der WSDL enthalten sein:

  <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 sollte eine Klasse generiert haben, die javax.xml.ws.Service beispielsweise mit Fooservice mit den entsprechenden Konstruktoren implementiert:

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

Irgendwo in Ihrem Code (hier einige Groovy), um das Lesen zu erleichtern, initialisieren Sie die obige Service-Instanz und rufen dann einen Port auf. Abhängig von der Markierung secure verwenden wir hier https oder 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()

Die Alternative, bei der die WSDL von einer Verbindung heruntergeladen wird, die von der für den Dienstaufruf verwendeten Verbindung getrennt ist (verwenden Sie tcpdump -n -nn -s0 -A -i eth0 'tcp port 80', um den Datenverkehr zu beobachten), ist Folgendes zu tun:

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

Beachten Sie, dass dies tatsächlich https verwendet, wenn wsdlLocationUrihttps angibt, obwohl wsService mit SERVICE_NAME_HTTP initialisiert wurde. (Nicht sicher warum - verwendet der Dienst das Schema, das zum Abrufen der WSDL-Ressource verwendet wird?)

Und das ist es auch schon.

Zum Debuggen der Verbindung übergeben Sie Folgendes:

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

an die JVM in der Befehlszeile. Dadurch werden Informationen aus dem http-Verbindungscode in stdout geschrieben (leider NICHT in Java.util.logging. Oracle, bitte!).

1
David Tonhofer

Meine Lösung bestand darin, den generierten Dienst zu ändern. Sie müssen wsdlLocation in der Header-Annotation ändern, und der Instantion-Block sieht folgendermaßen aus:

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

Ich platziere die wsdl-Datei im bin-Verzeichnis neben der ShipService-Klasse

0
IcedDante

Keine Notwendigkeit, irgendetwas zu komplizieren, verwenden Sie einfach jar classloader

ClassLoader cl = SomeServiceImplService.class.getClassLoader();
SERVICE_WSDL_LOCATION = cl.getResource("META-INF/wsdls/service.wsdl");

Versuch es!

0
lunicon

Obwohl Sie es mit einigen Manipulationen zum Laufen bringen können, empfehle ich nicht es zu machen und es so zu halten, wie Sie es gerade haben.

Web-Service-Endpunktanbieter sollten eine WSDL als Teil ihres Vertrags bereitstellen. Der von Ihnen generierte Code sollte von der WSDL vom Server selbst abgerufen werden.

Bei der Bereitstellung in WebSphere können Sie die Endpunkte von der Implementierungsbenutzeroberfläche aus auf andere Endpunkte ändern. Auf anderen Anwendungsservern müssen Sie möglicherweise die herstellerspezifische Bindungs-XML ermitteln, um dies zu tun.

Dies geschieht nur bei der Initialisierung, sodass die Auswirkungen auf Ihre gesamte Anwendung vernachlässigbar sein sollten.

0