Java: SOAP – Web Service Client mit variablen Endpoint (umgebungsunabhängig)

SOAP-Web-Service-ClientIn dem Beitrag: “Java: SOAP – Web Service Client schreiben” habe ich gezeigt, wie man mit Hilfe von JAX-WS in sehr kurzer Zeit einen schönen Web Service Client in Java programmieren kann. Jedoch hat der Beitrag einen Makel und zwar betrachtet er den Sachverhalt nur eindimensional.
Im Klartext: Der SOAP Web Service Client spricht nur mit dem Web Service Provider, der in der WSDL angegeben ist und das ist im Worst Case Localhost.
Möchte man nun den Web Service Client und den Web Service Provider trennen, hat man ein Problem. In diesem Beitrag erweitere ich meinen SOAP Web Service Client so, dass man beliebige Web Service Provider(Endpoints) ansteuern bzw. ansprechen kann. Dies ist auch die Grundlage für verteilte Anwendungen und das Umgebungskonzept.

 

Ausgangspunkt

In meinem Unternehmen habe ich einen Rechteck Web Service entwickelt. Dieser Rechteck-Web Service wurde von anderen Personen getestet und für den produktiven Einsatz freigeben. Da wir bei uns das Umgebungskonzept anwenden, habe ich also für jede Umgebung eine eigene WSDL-Adresse. Beispiel:

  • Entwicklungsumgebung
    • http://ex001:9202/rechteck/RechteckWebService?wsdl
  • Testumgebung
    • http://tx001:8202/rechteck/RechteckWebService?wsdl
  • Produktionsumgebung
    • http://px001:7202/rechteck/RechteckWebService?wsdl

 

unabhängige WSDL

In den letzten Beispielen (JAVA / PHP ) habe ich mir die WSDL immer direkt vom Web Service Provider (Endpoint) geholt. Da wir jetzt mehrere Endpoints haben und ich nicht für jeden Provider einen eigenen Client bauen möchte, lege ich die WSDL zentral an einer Stelle in meinem Web Service Client Projekt ab. Das hat den Vorteil, dass ich nicht auf einen Endpoint angewiesen bin und ich jederzeit mein Projekt bauen / kompilieren kann.
Die benötigte WSDL-Datei befindet sich beim Web Service selbst. Bei der Generierung der Schnittstellen-Klassen mit WSGEN werden automatisch die entsprechenden Dateien erzeugt. Bezogen auf das Rechteck Web Service Beispiel, befinden sich die Dateien im Verzeichnis /generated/wsdl:

RechteckWebService.wsdl

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Generated by JAX-WS RI at . RI's version is JAX-WS RI 2.1.6 in JDK 6. -->
<definitions targetNamespace="http://www.axxg.de/ws/rechteck" name="RechteckWebService" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://www.axxg.de/ws/rechteck" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
  <types>
    <xsd:schema>
      <xsd:import namespace="http://www.axxg.de/ws/rechteck" schemaLocation="RechteckWebService_schema1.xsd"/>
    </xsd:schema>
  </types>
  <message name="berechneUmfang">
    <part name="parameters" element="tns:berechneUmfang"/>
  </message>
  <message name="berechneUmfangResponse">
    <part name="parameters" element="tns:berechneUmfangResponse"/>
  </message>
  <message name="berechneInhalt">
    <part name="parameters" element="tns:berechneInhalt"/>
  </message>
  <message name="berechneInhaltResponse">
    <part name="parameters" element="tns:berechneInhaltResponse"/>
  </message>
  <portType name="RechteckWebService">
    <operation name="berechneUmfang">
      <input message="tns:berechneUmfang"/>
      <output message="tns:berechneUmfangResponse"/>
    </operation>
    <operation name="berechneInhalt">
      <input message="tns:berechneInhalt"/>
      <output message="tns:berechneInhaltResponse"/>
    </operation>
  </portType>
  <binding name="RechteckWebServicePortBinding" type="tns:RechteckWebService">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
    <operation name="berechneUmfang">
      <soap:operation soapAction=""/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
    <operation name="berechneInhalt">
      <soap:operation soapAction=""/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
  </binding>
  <service name="RechteckWebService">
    <port name="RechteckWebServicePort" binding="tns:RechteckWebServicePortBinding">
      <soap:address location="REPLACE_WITH_ACTUAL_URL"/>
    </port>
  </service>
</definitions>

RechteckWebService_schema1.xsd

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" targetNamespace="http://www.axxg.de/ws/rechteck" xmlns:tns="http://www.axxg.de/ws/rechteck" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="berechneInhalt" type="tns:berechneInhalt"/>

  <xs:element name="berechneInhaltResponse" type="tns:berechneInhaltResponse"/>

  <xs:element name="berechneUmfang" type="tns:berechneUmfang"/>

  <xs:element name="berechneUmfangResponse" type="tns:berechneUmfangResponse"/>

  <xs:complexType name="berechneInhalt">
    <xs:sequence>
      <xs:element name="Laenge" type="xs:float"/>
      <xs:element name="Breite" type="xs:float"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="berechneInhaltResponse">
    <xs:sequence>
      <xs:element name="Flaecheninhalt" type="xs:float"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="berechneUmfang">
    <xs:sequence>
      <xs:element name="Laenge" type="xs:float"/>
      <xs:element name="Breite" type="xs:float"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="berechneUmfangResponse">
    <xs:sequence>
      <xs:element name="Umfang" type="xs:float"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

1. Tipp: Im Namen der WSDL-Datei oder des Ordners sollte immer die Version des Web Services stehen!!! Sollte sich der Web Service / die Schnittstelle einmal ändern, kann man so den aktuellen Status des Clients nachvollziehen.
2. Tipp: Eine WSDL kann in beliebige Teile zerlegt werden. Also müssen alle Dateien kopiert werden!

 

JAX-WS WSImport

Genauso wie im anderen Beispiel besteht der nächste Schritt im Generieren der Schnittstellen-Klassen. Hierzu wird das Programm WSImport verwendet. Dieses Programm ist seit JDK 1.6 fester Bestandteil der Spezifikation und muss daher nicht nach installiert werden!
Um die Schnittstellen-Klassen zu generiert, muss das Target namens create-rechteck-service ausgeführt werden. Dieses befindet sich in der build.xml-Datei. (Wie ein Ant-Target ausgeführt wird, finden Sie hier.)
Folgende Anpassungen müssen noch durchgeführt werden:

  • Zeile 5: Der Pfad zum WSImport Tool. Dieser Pfad hängt von dem Betriebssystem und der installierten Java Version ab!
  • Zeile 10: Der Pfad zur WSDL. Es muss der Pfad zur lokalen WSDL angepasst werden.
...
	<property name="generated" value="generated" />
	<property name="generated.src" value="${generated}/src" />
	<property name="classes.dir" value="bin" />
	<property name="wsimport.exec" value="C:\Programme\Java\jdk1.6.0_27\bin\wsimport.exe" />

	<target name="create-rechteck-service">
		<antcall target="create-ws">
			<param name="package" value="de.axxg.rechteckws" />
			<param name="wsdl.location" value="resources/wsdl-V1_09-2012/RechteckWebService.wsdl" />
		</antcall>
	</target>
	

	<target name="create-ws">
		<exec executable="${wsimport.exec}">
			<arg value="-s" />
			<arg value="${generated.src}" />
			<arg value="-d" />
			<arg value="${classes.dir}" />
			<arg value="-p" />
			<arg value="${package}" />
			<arg value="${wsdl.location}" />

		</exec>
	</target>
...

 

Dynamische Web Service Endpoint URL

Nun kommen wir zu dem spannenden Teil dieses Beitrags – die Client-Klasse. Zunächst muss man dynmaisch die URL-Adresse des Web Service Providers / Endpoint besorgen. Mir fallen spontan folgende Möglichkeiten ein:

  • Umgebungsvariable
  • Startparameter
  • Konfigurationsdatei

Der Code dazu könnte so aussehen:

public static void main(String[] args) {

String wsdlUrl;

// Umgebungsvariable
wsdlUrl = System.getProperty("wsdlUrl");

// Startparameter
wsdlUrl = args[0];  

// Konfigfile
Properties myprop = new Properties();
myprop.load(new FileReader("C:/Pfad/zur/Properties-Datei/prop.properties"));
wsdlUrl = myprop.getProperty("wsdlUrl");

// Die Variable wsdlUrl haette je nach Ebene folgenden Wert:
wsdlUrl = "http://ex001:9202/rechteck/RechteckWebService?wsdl";
wsdlUrl = "http://tx001:8202/rechteck/RechteckWebService?wsdl";
wsdlUrl = "http://px001:7202/rechteck/RechteckWebService?wsdl";

 

Web Service Client erzeugen

Es gibt zwei gängige Möglichkeiten sich einen Web Service Client per JAX-WS zu erzeugen. Wie die Namen zustande kommen und wo Klassen herkommen könnt ihr hier nochmal nachlesen.

RechteckWebService_Service myRechteckWebService_Service;
RechteckWebService myService;
			
// Variante 1
// Der QName steht in den generierten Sourcen!!! (z.B.: generated/src/.../RechteckWebService_Service.java) 
myRechteckWebService_Service = new RechteckWebService_Service(new URL(wsdlUrl), new QName("http://www.axxg.de/ws/rechteck", "RechteckWebService"));
myService = myRechteckWebService_Service.getRechteckWebServicePort();
			
// Variante 2
// Achtung! Pfad muss angepasst werden!
File myWSDL = new File("C:/Pfad/zur/WSDL-Datei/RechteckWebService.wsdl");

// Der QName steht in den generierten Sourcen!!! (z.B.: generated/src/.../RechteckWebService_Service.java) 
myRechteckWebService_Service = new RechteckWebService_Service( myWSDL.toURI().toURL(),  new QName("http://www.axxg.de/ws/rechteck", "RechteckWebService"));
myService = myRechteckWebService_Service.getRechteckWebServicePort();

Map<String , Object> map = ((BindingProvider) myService).getRequestContext();
map.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, wsdlUrl);

 

Clientlogik

Der letzte Schritt ist das Implementieren der Clientlogik.

float ergUmfang = myService.berechneUmfang(4, 5);
float ergInhalt = myService.berechneInhalt(4, 5);
	
System.out.println("Flächeninhalt: " + ergInhalt);
System.out.println("Umfang: " + ergUmfang);

Nun muss ich das Projekt nur noch bauen, ausführen und …

…FERTIG!

 

Die kompletten Dateien

Main.java

package main;

import java.io.File;
import java.net.URL;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;

import de.axxg.rechteckws.RechteckWebService;
import de.axxg.rechteckws.RechteckWebService_Service;

public class Main {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		try{
			
			
			
			// Dynamisch die entsprechende WSDL-URL ermitteln
			String wsdlUrl;
			
			// Beipsiele
			//wsdlUrl = System.getProperty("wsdlUrl"); // Umgebungsvariable
			//wsdlUrl = args[0];  // Startparameter			
			/* Properties myprop = new Properties();
			 * myprop.load(new FileReader("C:/Pfad/zur/Properties-Datei/prop.properties"));
			 * wsdlUrl = myprop.getProperty("wsdlUrl");
			 */
			
			// Für das Beispiel hard ein Host eintragen
			wsdlUrl = "http://localhost:9202/rechteck/RechteckWebService";
		
			//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
			RechteckWebService_Service myRechteckWebService_Service;
			RechteckWebService myService;
			
			// Variante 1
			// Der QName steht in den generierten Sourcen!!! (z.B.: generated/src/.../RechteckWebService_Service.java) 
			myRechteckWebService_Service = new RechteckWebService_Service(new URL(wsdlUrl), new QName("http://www.axxg.de/ws/rechteck", "RechteckWebService"));
			myService = myRechteckWebService_Service.getRechteckWebServicePort();
			
			// Variante 2
			// Achtung! Pfad muss angepasst werden!
			File myWSDL = new File("C:/Pfad/zur/WSDL-Datei/RechteckWebService.wsdl");

			// Der QName steht in den generierten Sourcen!!! (z.B.: generated/src/.../RechteckWebService_Service.java) 
			myRechteckWebService_Service = new RechteckWebService_Service( myWSDL.toURI().toURL(),  new QName("http://www.axxg.de/ws/rechteck", "RechteckWebService"));
			myService = myRechteckWebService_Service.getRechteckWebServicePort();

			Map<String , Object> map = ((BindingProvider) myService).getRequestContext();
			map.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, wsdlUrl);
			
			
			
			// Eigentliche Clientlogik			
			float ergUmfang = myService.berechneUmfang(4, 5);
			float ergInhalt = myService.berechneInhalt(4, 5);
			
			System.out.println("Flächeninhalt: " + ergInhalt);
			System.out.println("Umfang: " + ergUmfang);
		}catch (Exception e) {
			// Fehlerhandling
			e.printStackTrace();
		}
	}

}


build.xml

<project name="RechteckWebServiceClient" default="release" basedir=".">
	<property name="jaxws-version" value="2.0" />
	<property name="jar.file" value="${ant.project.name}.jar"/>
	<property name="generated" value="generated" />
	<property name="generated.src" value="${generated}/src" />
	<property name="classes.dir" value="bin" />
	<property name="wsimport.exec" value="C:\Programme\Java\jdk1.6.0_27\bin\wsimport.exe" />


	<target name="clean">
		<delete failonerror="false" includeemptydirs="true">
			<fileset dir="${generated.src}" />
		</delete>
		<mkdir dir="${generated}" />
		<mkdir dir="${generated.src}" />
	</target>

	<target name="release" depends="clean,create-rechteck-service, jar" />

	<target name="create-rechteck-service">
		<antcall target="create-ws">
			<param name="package" value="de.axxg.rechteckws" />
			<param name="wsdl.location" value="resources/wsdl-V1_09-2012/RechteckWebService.wsdl" />
		</antcall>
	</target>
	
	<target name="jar">
		<jar destfile="release/${jar.file}">
			<zipfileset dir="${classes.dir}" includes="**/*.*" />
		</jar>
	</target>

	<target name="create-ws">
		<exec executable="${wsimport.exec}">
			<arg value="-s" />
			<arg value="${generated.src}" />
			<arg value="-d" />
			<arg value="${classes.dir}" />
			<arg value="-p" />
			<arg value="${package}" />
			<arg value="${wsdl.location}" />

		</exec>
	</target>

</project>

 

Beispiel

Web Service Client in Java mit varablen Endpoint

 

Quellen und weitere Informationen

 

Copyright © 2012 AxxG – Alexander Gräsel



Kommentar verfassen