EJB als Webservice
Inhalt:
Deklaration des Webservice
Der Service auf dem Server
Client-Anwendung vorbereiten
Client-Anwendung implementieren
Java 9-Modulsystem
Für WildFly 30 und JakartaEE 10: Dieses Beispiel zeigt, wie auf eine Stateless Session Bean per Webservice zugegriffen werden kann.
Es verwendet exakt die gleiche EJB-Schicht wie das Stateless-Beispiel, der einzige
Unterschied ist eine Annotation auf der EJB. Außerdem habe ich die EAR-Datei sowie die Namespaces so geändert, dass es parallel zum Stateless-Projekt
verwendet werden kann.
Hier gibt es das Webservice-Projekt als EAR-Export-Datei: StatelessService.ear.
Den Client findet man hier: StatelessServiceClient.zip. Dies ist ein gepacktes Eclipse-Java-Projekt.
Importanleitung: siehe Beispiel: Zugriff auf Stateless Session Bean aus Java-Anwendung
Deklaration des Webservice
Auf der Stateless Session Bean "GeometricModelBean" wird eine Annotation @jakarta.jws.WebService
definiert:
@Stateless()
@WebService()
public class GeometricModelBean implements GeometricModelRemote, GeometricModelLocal
{
Das war es auch schon...
Der Service auf dem Server
Beim Deploy sehen wir jetzt eine Statusausgabe
11:50:51,628 INFO [org.jboss.ws.cxf.metadata] (MSC service thread 1-5) JBWS024061: Adding service endpoint metadata: id=GeometricModelBean
address=http://localhost:8080/StatelessServiceEJB/GeometricModelBean
implementor=de.hsrm.jakartaee.knauf.statelessservice.GeometricModelBean
serviceName={http://statelessservice.knauf.jakartaee.hsrm.de/}GeometricModelBeanService
portName={http://statelessservice.knauf.jakartaee.hsrm.de/}GeometricModelBeanPort
annotationWsdlLocation=null
wsdlLocationOverride=null
mtomEnabled=false
Die automatisch erzeugte WSDL-Datei ("Web Services Description Language") finden wir unter der URL
localhost:8080/StatelessServiceEJB/GeometricModelBean?wsdl
und können sie z.B. im Browser anschauen.
Nach dem Deploy der Anwendung finden wir den Service in der WildFly-Konsole: auf den Karteireiter
"Runtime" gehen, dort links in der Spalte "Server" den Eintrag wählen, der dem eigenen Rechnernamen entspricht. In der zweiten Spalte "Monitor" wird der Eintrag
"Webservices" gewählt. In der dritten Spalten "Endpoint (1)" taucht jetzt die EJB auf. Man wählt sie aus und sieht rechts die Details des Webservice (z.B. die URL):
Client-Anwendung vorbereiten
Als Webservice-Client kann man hier ein beliebiges Framework verwenden, das den Zugriff auf Webservices erlaubt.
Ich habe mich für "Axis 2" (http://axis.apache.org/axis2/java/core/) entschieden. Im Folgenden wird nicht weiter
auf Details zu diesem Framework eingegangen, in diesem Beispiel geht es mir nur darum, ein möglichst simples Beispiel zu erstellen.
Das Beispiel basiert auf folgendem Axis2-Tutorial:
https://axis.apache.org/axis2/java/core/docs/userguide-creatingclients-xmlbeans.html
Vorbereitung: Download der Axis 2-Binaries von http://axis.apache.org/axis2/java/core/download.html
und entpacken.
Schritte für das Generieren des Client:
- Anlegen eines Java-Projekts in Eclipse (in meinem Fall heißt es "StatelessServiceClient").
- Kommandozeile öffnen und ins Wurzelverzeichnis des Client-Projekts wechseln.
- Folgende Befehle eingeben:
set JAVA_HOME=c:\temp\jdk-11.0.21
set AXIS2_HOME=c:\temp\axis2-1.8.2
%AXIS2_HOME%\bin\wsdl2java.bat -uri http://localhost:8080/StatelessServiceEJB/GeometricModelBean?wsdl -p de.hsrm.jakartaee.knauf.statelessservice.client
Die beiden Umgebungsvariablen müssen auf den korrekten Pfad angepasst werden.
Durch den Parameter "-p" wird das Ziel-Package angegeben. Dieses Package wird automatisch im Unterverzeichnis "src" angelegt.
Anmerkung: die oben verlinkte Webseite hat zusätzlich die Parameter "-d xmlbeans -s" verwendet. Ich habe sie weggelassen, und das Beispiel funktioniert trotzdem ;-).
Das Script führt zu folgender Ausgabe:
Using AXIS2_HOME: c:\temp\axis2-1.8.2
Using JAVA_HOME: c:\temp\jdk-11.0.14
Retrieving document at 'http://localhost:8080/StatelessServiceEJB/GeometricModelBean?wsdl'.
- Jetzt machen wir im Eclipse-Project Explorer ein "Refresh" und finden drei neue Quellcodedateien sowie "build.xml" vor:
Die drei Quellcodedateien enthalten die Client-Seite für den Service-Zugriff.
Die Datei "build.xml" können wir wieder löschen, da wir den Ant-Buildprozess nicht verwenden.
Leider wurden wir auch mit einem Haufen Java-Warnings beglückt. Diese kann man umgehen, indem man auf der Klasse GeometricModelBeanServiceStub
folgende Annotations setzt:
@SuppressWarnings({"unchecked", "rawtypes", "unused", "serial"})
public class GeometricModelBeanServiceStub extends org.apache.axis2.client.Stub {
- Es muss ein Haufen JAR-Dateien aus dem Axis2-Paket zu den Projekt-Libraries zugefügt werden (Screenshot ist noch auf Stand von Axis 1.8.0):
Es sind:
- axiom-api-1.4.0.jar
- axiom-impl-1.4.0.jar
- axis2-adb-1.8.2.jar
- axis2-kernel-1.8.2.jar
- axis2-transport-http-1.8.2.jar
- axis2-transport-local-1.8.2.jar
- commons-logging-1.2.jar
- httpclient-4.5.13.jar
- httpcore-4.4.15.jar
- jakarta.activation-1.2.1.jar
- neethi-3.2.0.jar
- woden-core-1.0M10.jar
- wsdl4j-1.6.3.jar
- xmlschema-core-2.3.0.jar
Diese Liste ist durch try/error entstanden. Zum Teil führte das Fehlen von JAR-Dateien zu Compilefehlern, zum Teil zu Laufzeitfehlern. Wenn weitere/andere Funktionen des Axis2-Framework genutzt werden sollen oder eine anderen Version genutzt
wird, dann kann sie sich durchaus ändern. Mit Maven wäre das nicht passiert ;-)
Zusätzlich kann man noch "axis2-jaxws-1.8.2.jar" einbinden, um folgende Warnung zu umgehen:
Feb. 23, 2022 7:22:29 NACHM. org.apache.axis2.deployment.AxisConfigBuilder processDeployers
WARNUNG: Unable to instantiate deployer org.apache.axis2.deployment.ServiceDeployer; see debug logs for more details
Client-Anwendung implementieren
Wir fügen eine neue Klasse mit einer "main"-Methode zu, in meinem Beispiel heißt sie "StatelessClient".
Die Main-Methode hat diesen Code:
try
{
System.out.println("STARTED...");
GeometricModelBeanServiceStub stub = new GeometricModelBeanServiceStub("http://localhost:8080/StatelessServiceEJB/GeometricModelBean?wsdl");
System.out.println("Executing computeCuboidSurface...");
ComputeCuboidSurfaceE requestSurface = new ComputeCuboidSurfaceE();
ComputeCuboidSurface requestParamSurface = new ComputeCuboidSurface();
requestParamSurface.setArg0(10);
requestParamSurface.setArg1(20);
requestParamSurface.setArg2(30);
requestSurface.setComputeCuboidSurface(requestParamSurface);
ComputeCuboidSurfaceResponseE responseSurface = stub.computeCuboidSurface(requestSurface);
System.out.println("Surface: " + responseSurface.getComputeCuboidSurfaceResponse().get_return());
System.out.println("Executing computeCuboidVolume...");
ComputeCuboidVolumeE requestVolume = new ComputeCuboidVolumeE();
ComputeCuboidVolume requestParamVolume = new ComputeCuboidVolume();
requestParamVolume.setArg0(10);
requestParamVolume.setArg1(20);
requestParamVolume.setArg2(30);
requestVolume.setComputeCuboidVolume(requestParamVolume);
ComputeCuboidVolumeResponseE responseVolume = stub.computeCuboidVolume(requestVolume);
System.out.println("Volume: " + responseVolume.getComputeCuboidVolumeResponse().get_return());
}
catch (Exception e)
{
e.printStackTrace();
}
Im Detail:
GeometricModelBeanServiceStub stub = new GeometricModelBeanServiceStub("http://localhost:8080/StatelessServiceEJB/GeometricModelBean?wsdl");
Dies erzeugt eine Instanz des Client-Stub. Die Service-URL kann man hier überdefinieren, man könnte allerdings auch den parameterlosen
Konstruktor verwenden, der dann die URL verwendet, die zum Zeitpunkt des Generieren des Client-Stub gültig war.
ComputeCuboidSurfaceE requestSurface = new ComputeCuboidSurfaceE();
ComputeCuboidSurface requestParamSurface = new ComputeCuboidSurface();
requestParamSurface.setArg0(10);
requestParamSurface.setArg1(20);
requestParamSurface.setArg2(30);
requestSurface.setComputeCuboidSurface(requestParamSurface);
ComputeCuboidSurfaceResponseE responseSurface = stub.computeCuboidSurface(requestSurface);
System.out.println("Surface: " + responseSurface.getComputeCuboidSurfaceResponse().get_return());
Dieser Code ruft die Servicemethode "computeCuboidSurface" auf. Der Code ist umständlich, weil die Service-Methode nur einen einzigen
Parameter vom Typ "ComputeCuboidSurfaceE" hat, der wiederum eine Klasse vom Typ "ComputeCuboidSurface" kapselt, die alle einzelnen
Methodenparameter (durchnummeriert, nicht benannt!) als Properties enthält.
Umgekehrt ist es mit der Rückgabe: die Servicemethode gibt eine Klasse "ComputeCuboidSurfaceResponseE" zurück, die wiederum eine weitere Klasse
"ComputeCuboidSurfaceResponse" kapselt, die eine Property "return" hat, in der der Rückgabewert des Service steckt.
Analog sieht es für den Aufruf von "computeCuboidVolume" aus.
Java 9-Modulsystem
Um eine "module-info.java" zu erzeugen, wird in Eclipse der Kontextmenüeintrag "Configure" => "Create module-info.java" aufgerufen:
In dieser "module-info.java" werden diverse Einträge für Automatic Modules der Axis2-Libraries erzeugt:
module StatelessServiceClient
{
exports de.hsrm.jakartaee.knauf.statelessservice.client;
requires axiom.api;
requires axis2.adb;
requires axis2.kernel;
requires commons.logging;
requires jakarta.activation;
requires java.rmi;
requires java.xml;
}
Das führt leider zu vier Warnungen, die sich nicht umgehen lassen:
Name of automatic module 'axiom.api' is unstable, it is derived from the module's file name.
Name of automatic module 'axis2.adb' is unstable, it is derived from the module's file name.
Name of automatic module 'axis2.kernel' is unstable, it is derived from the module's file name.
Name of automatic module 'commons.logging' is unstable, it is derived from the module's file name.
Ich habe dazu https://issues.apache.org/jira/browse/AXIS2-6040 im Axis2-Bugtracker erfasst.
Außerdem gibt es weitere Warnungen dieser Form:
The type ConfigurationContext from module axis2.kernel may not be accessible to clients due to missing 'requires transitive'
Diese lassen sich einfach umgehen: zwei "requires"-Einträgen werden durch "requires transitive" ersetzt:
module StatelessServiceClient
{
exports de.hsrm.jakartaee.knauf.statelessservice.client;
requires axiom.api;
requires axis2.adb;
requires transitive axis2.kernel;
requires commons.logging;
requires jakarta.activation;
requires java.rmi;
requires transitive java.xml;
}
Stand 01.01.2024
Historie:
01.01.2024: Erstellt aus JavaEE6-Beispiel