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):
WildFly-Konsole


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:


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

Axis2 ist erst ab Version 1.8.2 kompatibel zum Java9-Modulsystem: https://issues.apache.org/jira/browse/AXIS2-4311


Um eine "module-info.java" zu erzeugen, wird in Eclipse der Kontextmenüeintrag "Configure" => "Create module-info.java" aufgerufen:
module-info.java
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