Uruchomienie zdalnego klienta EJB wyłącznie w oparciu o interfejs biznesowy (bez implementacji)

Z Jacek Laskowski - Wiki Projektanta Java EE

Właśnie takiego obrotu sprawy sobie życzyłem. Publikuję wyniki moich doświadczeń szerszemu gronu z nadzieją, że komukolwiek zechce się poświęcić trochę czasu na prześledzenie mojego toku myślenia i samych wyników. Tak też było w przypadku mojego, ostatniego artykułu Jak długo korzystać z referencji bezstanowego komponentu sesyjnego EJB (na przykładzie EJB 3.0 i GlassFish v3). Jego celem było sprawdzenie zachowania się zdalnej referencji EJB3 podczas niedostępności serwera. Przeoczyłem jednak fakt, że aplikacja kliencka dystrybuowana była nie tylko ze zdalnym interfejsem biznesowym, ale również z jego implementacją jako bezstanowe ziarno sesyjne EJB. Nie długo trzeba było czekać, aby takiemu podejściu zaprotestował Łukasz Lenart:

Jedna uwaga, która ogólnie jest globalna do tego typu przykładów. Mianowicie klientowi dostarczasz nie tylko interfejs ale również implementację jako zależność, przez co używanie serwera aplikacyjnego do zdalnej komunikacji jest zbędnym narzutem. Nigdzie jeszcze nie znalazłem przykładu jak to zrobić bez dostarczania implementacji a tylko sam interfejs. Moje próby potwierdziły tylko, że tego nie da się zrobić w przypadku Glassfisha :-(

Problem, jaki wskazał Łukasz, polegał na dystrybuowaniu zdalnego klienta EJB3 w paczce, która zawierała nie tylko interfejs biznesowy, ale i jego implementację (!) Przyznaję, że nie pomyślałem o takim skonstruowaniu paczki dystrybucyjnej klienta, która nie zawierałaby implementacji komponentu EJB, z którego korzysta. Kiedy przeczytałem komentarz, a szczególnie jego ostatnie zdanie, nie miałem złudzeń czym się zająć.

Rozproszona komunikacja klient-komponent EJB nie wymaga poznawania szczegółów implementacyjnych tego drugiego, a jedynie udostępnionego interfejsu biznesowego. To jest właśnie kontrakt komunikacji między nimi. Zdalny klient korzysta z pośrednika, który obsługuje warstwę transportową, a to uwalnia stronę serwerową od jakiegokolwiek związku z klientem poza samym kontraktem komunikacji, czyli interfejsem (komunikacji w sensie dostępnych metod i ich sygnatur, a nie protokołu). I na odwrót, klient nie jest w żadnen sposób związany z implementacją działającą na serwerze. Co więcej, implementacja komponentu EJB (uruchomiona na serwerze) może się zmienić w międzyczasie, wliczając w to moment, w którym sam klient jest w trakcie działania. To nie powinno dziwić. Takie są założenia zdalnej komunikacji opartej na kontrakcie opisanym w postaci interfejsu zdalnego. Gdyby zastanowić się nad tym bardziej, taka sama semantyka działania interfejsu jest w samej Java SE, gdzie podmiana implementacji nie powinna wpływać w żadnej sposób na działanie klienta. Mówimy o takim budowaniu aplikacji, aby szczegóły implementacyjne były ukryte za kurtyną interfejsu.

Jak to ma zwykle miejsce w tego rodzaju aplikacjach, szczególnie tych demonstracyjnych, w tym samym module projektowym dla komponentu EJB był interfejs i jego domyślna implementacja (jako usprawiedliwienie takiego podejścia mogę jedynie wskazać podział pakietów, w którym implementacja była we własnym, co może wskazywać, że choć przez chwilę przemknęła mi myśl o dobrych praktykach). Wybór różnych pakietów był podyktowany ostatnimi doświadczeniami z OSGi, gdzie rozdziela się interfejs usługi od jego implementacji. Pojedyncza usługa (w sensie bytu oferującego pewną funkcjonalność) odpowiadają co najmniej dwa pakunki OSGi (powód możnaby również nazwać ograniczeniem OSGi, w którym udostępnianiu podlegają wyłącznie pakiety, a więc gdyby interfejs i jego implementacja była w jednym, nieświadomie udostępilibyśmy również samą implementację). Uwaga Łukasza idzie dokładnie w kierunku rozwiązań modularnych proponowanych przez OSGi (i znając Łukasza pewnie i przez jakieś tam wymyślne wzorce i dobre praktyki :)). Wymusił na mnie tym samym postawienie kolejnego kroku, który polegał na rozszczepieniu jednolitego modułu projektowego EJB na dwa - jeden zawierający interfes, a drugi implementację. W przypadku EJB3 połączenie obu składowych może wydawać się początkowo problematyczne, ale po prostu ta technologia i sama platforma Javy tak ma (i co znowu możnaby nazwać ich ograniczeniem, ale świadomie nie rozwijam tematu, bo zaczynam odbiegać od sedna sprawy).

Przy rozwiązaniu sugerowanym przez Łukasza cała aplikacja składa się teraz z modułu EJB3 będącego sklejeniem dwóch innych - modułu interfejsu zdalnego (bez jakichkolwiek technologicznych dodatków, bo w końcu to tylko interfejs i niech tak zostanie) oraz modułu implementacji, w której skorzystano z adnotacji EJB3. Całość spięta jest za pomocą kolejnego modułu projektowego EAR, który w specyfikacji Java EE jest doskonałym "nośnikiem" obu w postaci jednego pliku. Połączenie modułów następuje poprzez wpis Class-Path w MANIFEST.MF (cóż za zbieżność z podobnymi rozwiązaniami w OSGi). Są inne rozwiązania, bardziej specyficzne dla Java EE, ale takie rozwiązanie wystarczy. Zainteresowanych dalszą lekturą zapraszam do specyfikacji Java EE 5 do rozdziału EE.8.2 Library Support (strona 155).

Zakładam, że struktura projektowa jest dokładnie taka, jak opisana w Jak długo korzystać z referencji bezstanowego komponentu sesyjnego EJB (na przykładzie EJB 3.0 i GlassFish v3). W zasadzie możnaby powiedzieć, że artykuł jest lekturą obowiązkową przed lekturą tego.

Kompletny projekt jest dostępny do pobrania jako ejb3-zdalnyklient-glassfish3-v2.zip.

Spis treści

Zestawienie struktury katalogowej projektu - ejb3-zdalnyklient-glassfish3-v2

Stworzenie nowego podprojektu zdalnego interfejsu biznesowego - ejb3-intf

Pobieramy poprzedni projekt ejb3-zdalnyklient-glassfish3.zip i rozpakowujemy do wybranego katalogu, np. ejb3-zdalnyklient-glassfish3-v2. Od tej pory wszystkie polecenia będą wykonywane z poziomu tego katalogu.

Tworzymy nowy projekt ejb3-intf, w którym będzie znajdował się interfejs biznesowy.

jlaskowski@work /cygdrive/c/projs/sandbox/ejb3-zdalnyklient-glassfish3-v2
$ mvn archetype:generate -B \
   -DarchetypeArtifactId=maven-archetype-quickstart \
   -DgroupId=pl.jaceklaskowski.javaee \
   -DartifactId=ejb3-intf \
   -Dversion=1.0

Kasujemy tworzone domyślnie przez mvn archetype:generate klasy App.test oraz AppTest.java.

jlaskowski@work /cygdrive/c/projs/sandbox/ejb3-zdalnyklient-glassfish3-v2
$ find ejb3-intf -name App*.java | xargs rm

Przenosimy interfejs pl.jaceklaskowski.javaee.WyswietlanieKomunikatow z projektu ejb3 do ejb3-intf.

jlaskowski@work /cygdrive/c/projs/sandbox/ejb3-zdalnyklient-glassfish3-v2
$ mv ejb3/src/main/java/pl/jaceklaskowski/javaee/WyswietlanieKomunikatow.java ejb3-intf/src/main/java/pl/jaceklaskowski/javaee/

Ostatnia zmiana dotycząca projektu ejb3-intf dotyka interfejsu WyswietlanieKomunikatow (plik ejb3-intf/src/main/java/pl/jaceklaskowski/javaee/WyswietlanieKomunikatow.java), tak aby nie zawierał zależności technologicznych związanych z EJB3 - usuwamy adnotację @Remote. Będzie to wyłącznie interfejs javowy bez jakichkolwiek dodatków technologicznych.

package pl.jaceklaskowski.javaee;

public interface WyswietlanieKomunikatow {
  void wyswietl(String komunikat);
}

Zmiany w projekcie ejb3

W nowozałożonym projekcie ejb3-intf zdjęliśmy zależność technologiczną z interfejsu WyswietlanieKomunikatow przez usunięcie adnotacji @Remote. Zgodnie ze specyfikacją EJB 3.0 wskazanie rodzaju interfejsu - zdalny czy lokalny - jest w samym interfejsie lub klasie realizującej go. Nie ma jej w interfejsie, więc konieczne jest dołożenie adnotacji @Remote do implementacji ziarna EJB - pl.jaceklaskowski.javaee.impl.DomyslneWyswietlanieKomunikatow w projekcie implementacji - ejb3 (plik ejb3/src/main/java/pl/jaceklaskowski/javaee/impl/DomyslneWyswietlanieKomunikatow.java).

package pl.jaceklaskowski.javaee.impl;

import java.util.logging.Logger;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import pl.jaceklaskowski.javaee.WyswietlanieKomunikatow;

@Stateless(mappedName="WyswietlanieKomunikatow")
@Remote
public class DomyslneWyswietlanieKomunikatow implements WyswietlanieKomunikatow {

  Logger logger = Logger.getLogger(getClass().toString());

  public void wyswietl(String komunikat) {
    logger.info(komunikat);
  }
}

Przechodzimy do wprowadzenia kilku zmian w ejb3/pom.xml. Dodajemy zależność między projektami ejb3-intf a ejb3 oraz konfigurujemy wtyczkę maven-ejb-plugin, aby ustaliła zależności między modułami dla środowiska uruchomieniowego przez dodanie dyrektywy Class-Path do META-INF/MANIFEST.MF.

Plik ejb3/pom.xml powinien wyglądać następująco:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <artifactId>ejb3-zdalnyklient-glassfish3</artifactId>
    <groupId>pl.jaceklaskowski.javaee</groupId>
    <version>1.0</version>
  </parent>
  <groupId>pl.jaceklaskowski.javaee</groupId>
  <artifactId>ejb3</artifactId>
  <packaging>ejb</packaging>
  <version>1.0</version>
  <name>ejb3</name>
  <url>http://www.JacekLaskowski.pl</url>
  <dependencies>
    <dependency>
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-ejb_3.0_spec</artifactId>
      <version>1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>pl.jaceklaskowski.javaee</groupId>
      <artifactId>ejb3-intf</artifactId>
      <version>1.0</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-ejb-plugin</artifactId>
        <configuration>
          <ejbVersion>3.0</ejbVersion>
          <archive>
            <manifest>
              <addClasspath>true</addClasspath>
            </manifest>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Niecierpliwi efektów mogą uruchomić polecenie mvn clean install, aby upewnić się, że wszystko jest w należytym porządku.

jlaskowski@work /cygdrive/c/projs/sandbox/ejb3-zdalnyklient-glassfish3-v2
$ mvn clean install
...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] ejb3-zdalnyklient-glassfish3 .......................... SUCCESS [4.188s]
[INFO] ejb3-intf ............................................. SUCCESS [4.406s]
[INFO] ejb3 .................................................. SUCCESS [1.047s]
[INFO] klient ................................................ SUCCESS [7.484s]
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

Utworzenie nowego podprojektu spinającego - ear

Po rozszczepieniu pierwotnego modułu ejb3 z obiema klasami - interfejsu i implementacji na dwa moduły ejb3-intf z interfejsem i ejb3 z implementacją, konieczne jest stworzenie dodatkowego projektu spinającego oba moduły o nazwie ear. Jego produktem będzie plik EAR z dwoma plikami jar wewnątrz - pierwszy z interfejsem biznesowym (produkt projektu ejb3-intf), a drugi z implementacją (produkt projektu ejb3). Taki sposób jest jednym z kilku zalecanych sposobów dystrybucji modułów z ich zależnościami.

jlaskowski@work /cygdrive/c/projs/sandbox/ejb3-zdalnyklient-glassfish3-v2
$ mvn archetype:generate -B \
   -DarchetypeArtifactId=maven-archetype-quickstart \
   -DgroupId=pl.jaceklaskowski.javaee \
   -DartifactId=ear \
   -Dversion=1.0

Pora na trochę (meta)programowania w XML edytując ear/pom.xml. Zmieniamy packaging na ear wskazując Mavenowi, że mamy do czynienia z projektem typu EAR, dodajemy zależności między projektami ear i ejb3 (który z kolei zależy od ejb3-intf) oraz konfigurujemy wtyczkę maven-ear-plugin - wtyczkę odpowiedzialną za zbudowanie poprawnego EARa.

Ostatecznie plik ear/pom.xml powinien prezentować się następująco:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <artifactId>ejb3-zdalnyklient-glassfish3</artifactId>
    <groupId>pl.jaceklaskowski.javaee</groupId>
    <version>1.0</version>
  </parent>
  <groupId>pl.jaceklaskowski.javaee</groupId>
  <artifactId>ear</artifactId>
  <packaging>ear</packaging>
  <version>1.0</version>
  <name>ear</name>
  <url>http://www.JacekLaskowski.pl</url>
  <dependencies>
    <dependency>
      <groupId>pl.jaceklaskowski.javaee</groupId>
      <artifactId>ejb3</artifactId>
      <version>1.0</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-ear-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <version>5</version>
          <generateApplicationXml>false</generateApplicationXml>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

UWAGA: Określenie wersji wtyczki maven-ear-plugin na 2.3.2 wydaje się być niezbędne, aby Maven poprawnie zbudował paczkę. Zarządzanie wersjami wtyczek zaleca się prowadzić przez główny pom.xml w sekcji pluginManagement w elemencie build, np.

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-ear-plugin</artifactId>
      <version>2.3.2</version>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-ejb-plugin</artifactId>
      <version>2.2</version>
    </plugin>
  </plugins>
</pluginManagement>

Zmiany w projekcie klienta

Pozostaje zmienić projekt klienta, aby był zależny od projektu interfejsu ejb3-intf, co odzwierciedlamy w klient/pom.xml przez zmianę nazwy artifactId z ejb3 na ejb3-intf w jednej z zależności. Jest to jedyna zmiana.

Cały plik klient/pom.xml wygląda następująco:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <artifactId>ejb3-zdalnyklient-glassfish3</artifactId>
    <groupId>pl.jaceklaskowski.javaee</groupId>
    <version>1.0</version>
  </parent>
  <groupId>pl.jaceklaskowski.javaee</groupId>
  <artifactId>klient</artifactId>
  <version>1.0</version>
  <name>klient</name>
  <url>http://www.JacekLaskowski.pl</url>
  <properties>
    <glassfish.home>c:/apps/glassfishv3/glassfish</glassfish.home>
  </properties>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>ejb3-intf</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>glassfish</groupId>
      <artifactId>appserv-rt.jar</artifactId>
      <version>LATEST</version>
      <scope>system</scope>
      <systemPath>${glassfish.home}/lib/appserv-rt.jar</systemPath>
    </dependency>
    <dependency>
      <groupId>glassfish</groupId>
      <artifactId>javaee.jar</artifactId>
      <version>LATEST</version>
      <scope>system</scope>
      <systemPath>${glassfish.home}/lib/javaee.jar</systemPath>
    </dependency>
    <dependency>
      <groupId>glassfish</groupId>
      <artifactId>jndi-properties.jar</artifactId>
      <version>LATEST</version>
      <scope>system</scope>
      <systemPath>${glassfish.home}/lib/jndi-properties.jar</systemPath>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <executions>
          <execution>
            <id>default-cli</id>
            <configuration>
              <includes>
                <include>**/WyswietlanieKomunikatowKlientTest.java</include>
              </includes>
            </configuration>
          </execution>
          <execution>
            <id>default-test</id>
            <configuration>
              <excludes>
                <exclude>**/WyswietlanieKomunikatowKlientTest.java</exclude>
              </excludes>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Uruchomienie środowiska - Uwaga! Doświadczenie w toku

Wdrożenie paczki ear na serwer GlassFish

Pozostaje wdrożyć projekt ear na serwer GlassFish i uruchomić klienta.

Budujemy cały projekt poleceniem mvn clean install.

jlaskowski@work /cygdrive/c/projs/sandbox/ejb3-zdalnyklient-glassfish3-v2
$ mvn clean install
...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] ejb3-zdalnyklient-glassfish3 .......................... SUCCESS [1.765s]
[INFO] ejb3-intf ............................................. SUCCESS [1.407s]
[INFO] ejb3 .................................................. SUCCESS [0.609s]
[INFO] klient ................................................ SUCCESS [3.312s]
[INFO] ear ................................................... SUCCESS [0.313s]
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

Uruchamiamy GlassFisha, jeśli jeszcze nie działa i wykonujemy polecenie asadmin deploy ze wskazaniem na paczkę ear-1.0.ear.

jlaskowski@work /cygdrive/c/projs/sandbox/ejb3-zdalnyklient-glassfish3-v2
$ c\:/apps/glassfishv3/bin/asadmin.bat deploy ear/target/ear-1.0.ear

Command deploy executed successfully.

UWAGA: Może się zdarzyć, że wdrożenie zakończy się niepowodzeniem.

jlaskowski@work /cygdrive/c/projs/sandbox/ejb3-zdalnyklient-glassfish3-v2
$ c\:/apps/glassfishv3/bin/asadmin.bat deploy ear/target/ear-1.0.ear
com.sun.enterprise.admin.cli.CommandException: remote failure: Exception while loading the app : java.lang.RuntimeException: EJB Container initialization error
Exception while invoking class org.glassfish.ejb.startup.EjbDeployer load method : java.lang.RuntimeException: EJB Container initialization error

Command deploy failed.

Należy sprawdzić dziennik serwera - glassfish/domains/domain1/logs/server.log - w poszukiwaniu przyczyny, aczkolwiek bardzo prawdopodobnym będzie, że ma to związek z naszymi ostatnimi doświadczeniami, przez co związanie komponentu EJB3 będzie niemożliwe, gdyż jego nazwa JNDI jest już zajęta. Odinstalowujemy poprzednią aplikację poleceniem asadmin undeploy ejb3-1.0.

jlaskowski@work /cygdrive/c/projs/sandbox/ejb3-zdalnyklient-glassfish3-v2
$ c\:/apps/glassfishv3/bin/asadmin.bat undeploy ejb3-1.0

Command undeploy executed successfully.

Uruchomienie zdalnego klienta

Wszystko gotowe do finalnego kroku. Jesteśmy gotowi do uruchomienia zdalnego klienta. Uruchamiamy polecenie mvn -X surefire:test, tj. z parametrem -X, który wyświetli ścieżkę klas, która jest wykorzystywana przez klienta.

UWAGA: Parametr -X włącza najniższy poziom komunikatów - tryb DEBUG, więc ich liczba może wpłynąć na Twój stan emocjonalny :)

jlaskowski@work /cygdrive/c/projs/sandbox/ejb3-zdalnyklient-glassfish3-v2
$ mvn -X surefire:test
...
[INFO] [surefire:test {execution: default-cli}]
...
[DEBUG] Test Classpath :
[DEBUG]   C:\projs\sandbox\ejb3-zdalnyklient-glassfish3\klient\target\test-classes
[DEBUG]   C:\projs\sandbox\ejb3-zdalnyklient-glassfish3\klient\target\classes
[DEBUG]   C:\.m2\junit\junit\4.2\junit-4.2.jar
[DEBUG]   C:\.m2\pl\jaceklaskowski\javaee\ejb3-intf\1.0\ejb3-intf-1.0.jar
[DEBUG]   c:\apps\glassfishv3\glassfish\lib\appserv-rt.jar
[DEBUG]   c:\apps\glassfishv3\glassfish\lib\javaee.jar
[DEBUG]   c:\apps\glassfishv3\glassfish\lib\jndi-properties.jar
[DEBUG] Setting system property [user.dir]=[C:\projs\sandbox\ejb3-zdalnyklient-glassfish3\klient]
[DEBUG] Setting system property [localRepository]=[C:\.m2]
[DEBUG] Setting system property [basedir]=[C:\projs\sandbox\ejb3-zdalnyklient-glassfish3\klient]
[DEBUG] Using JVM: c:\apps\java6\jre\bin\java
[INFO] Surefire report directory: C:\projs\sandbox\ejb3-zdalnyklient-glassfish3\klient\target\surefire-reports
Forking command line: cmd.exe /X /C "c:\apps\java6\jre\bin\java -jar c:\temp\surefirebooter9119675162328699267.jar 
c:\temp\surefire3159341923854909018tmp c:\temp\surefire7658732724747609475tmp"

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running pl.jaceklaskowski.javaee.WyswietlanieKomunikatowKlientTest
+++ Kontekst JNDI utworzony
2009-10-08 20:05:05 com.sun.enterprise.transaction.JavaEETransactionManagerSimplified initDelegates
INFO: Using com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate as the delegate
+++ Pobranie zdalnej referencji wykonane poprawnie
iteracja 1

Podsumowanie

Jak widać w komunikatach polecenia mvn -X surefire:test po [DEBUG] Test Classpath : ścieżka klas składa się wyłącznie z modułu interfejsu - C:\.m2\pl\jaceklaskowski\javaee\ejb3-intf\1.0\ejb3-intf-1.0.jar, bez wskazania na moduł implementacji. Poprawne uruchomienie klienta było możliwe wyłącznie w oparciu o zdalny interfejs biznesowy, bez udziału implementacji ziarna EJB3. Tym samym uznaję doświadczenie za zakończone poprawnie.

Łukasz, jeszcze raz dziękuję za pomysł! Ufam, że opisanym doświadczeniem spłaciłem swój dług wdzięczności ;-)

Osobiste