niedziela, 22 marca 2009

Ziarna sesyjne - czar prysł.

Dopiero co zachwycałem się nad lokalnym i zdalnym sposobem wywoływania usług ziaren EJB. Przyszła wiosna, śnieg stopniał i czar prysł. Otóż pomiędzy wywołaniem danej metody poprzez interfejs oznaczony adnotacją @Local a wywołaniem za pomocą interfejsu @Remote mogą być subtelne różnice. Bierze się to stąd, że parametry metod wywoływanych lokalnie (@Local) przekazywane są poprzez referencje, parametry metod zdalnych (@Remote) przez wartość.

Zobaczmy o co chodzi na podstawie prostego przykładu. Stworzę z tej okazji w NetBeans IDE 6.5.1 projekt ziarna EJB oraz projekt klienta EJB.

Zaczynam od aplikacji EJB:
Z menu File wybieram pozycję New Project:


następnie wybieram kategorię Java EE i projekt EJB Module
klikam Next, po czym na zakładce Server and Settings dodaję nową konfigurację serwera, klikając przycisk Add...

Przykład będę uruchamiał na serwerze JBoss 4.2.2.GA


więc wskazuję jego położenie na dysku:


klikam Next> i Finish. Projekt gotowy.

W projekcie stworzę proste ziarno, które będzie udostępniało metodę dodającą podany jako parametr element do podanej jako parametr kolekcji:


public Collection add(Collection collection, Object element) {
collection.add(element);
return collection;
}


Ziarno będzie implementowało interfejs lokalny oraz zdalny. Ziarno oraz interfejsy przedstawiają się następująco:

Kod ziarna StatelessBean:

package pl.matt.session;

import java.util.Collection;
import javax.ejb.Local;
import javax.ejb.Remote;


@javax.ejb.Stateless
@Remote(StatelessRemote.class)
@Local(StatelessLocal.class)
public class StatelessBean implements StatelessRemote, StatelessLocal {

public Collection add(Collection collection, Object element) {
collection.add(element);
return collection;
}

}


interfejs zdalny StatelessRemote:

package pl.matt.session;

import java.util.Collection;
import javax.ejb.Remote;

public interface StatelessRemote {

Collection add(Collection collection, Object element);

}


i identyczny interfejs lokalny StatelessLocal:

package pl.matt.session;

import java.util.Collection;
import javax.ejb.Remote;

public interface StatelessLocal {

Collection add(Collection collection, Object element);

}


Całość uzupełnia ziarno testujące dodawanie elementów do kolekcji, które próbuje zarówno zdalnie jak i lokalnie dodać 3 napisy do listy:

interfejs TestRemote:

package pl.matt.session;

public interface TestRemote {
void test();
}


oraz jego implementacja:


package pl.matt.session;

import java.util.ArrayList;
import java.util.List;
import javax.ejb.Remote;
import javax.naming.Context;
import javax.naming.InitialContext;

@javax.ejb.Stateless
@Remote(TestRemote.class)
public class TestBean implements TestRemote {

public void test() {
try {
Context context = new InitialContext();
StatelessLocal statelessLocal = (StatelessLocal) context.lookup("StatelessBean/local");
StatelessRemote statelessRemote = (StatelessRemote) context.lookup("StatelessBean/remote");
List<String> localList = new ArrayList();
statelessLocal.add(localList, "1");
statelessLocal.add(localList, "2");
statelessLocal.add(localList, "3");
System.out.println("local list: " + localList);

List<String> remoteList = new ArrayList();
statelessRemote.add(remoteList, "1");
statelessRemote.add(remoteList, "2");
statelessRemote.add(remoteList, "3");
System.out.println("remote list: " + remoteList);
} catch (Exception e) {
e.printStackTrace();
}
}
}


Do wywołania metody ziarna TestBean potrzebuję klienta EJB. Tworzę go wybierając ponownie pozycję New Project... z menu File. Następnie z kategorii Java EE wybieram projekt Enterprise Application Client:

Do tak stworzonego projektu dodaję jako zależność stworzony uprzednio projekt EJB


Dzięki temu, będę w nim mógł uzyskać dostęp do interfejsu testującego. Całość kodu klienta zawarta jest w metodzie main():


package pl.matt;

import javax.naming.Context;
import javax.naming.InitialContext;
import pl.matt.session.TestRemote;

public class Main {

public static void main(String[] args) {
try {
System.out.println("start");
Context context = new InitialContext();
TestRemote test = (TestRemote) context.lookup("TestBean/remote");
test.test();
System.out.println("finished");
} catch (Exception e) {
e.printStackTrace();
}

}
}


Pozostało uruchomienie i przekonanie się, jak to wszystko będzie działać. Klikam prawym przyciskiem myszy na projekcie EJB, wybieram opcję Run. Następnie to samo robię na projekcie klienta EJB. JBoss się uruchamia, po chwili widzę w konsoli:


22:27:55,969 INFO [STDOUT] local list: [1, 2, 3]
22:27:55,974 INFO [STDOUT] remote list: []


2 razy wywołałem tę samą metodę, raz przez interfejs @Remote, raz przez @Local i wynik jej działania jest inny. Do kolekcji, przekazanej przez wartość elementy nie zostały dodane (tzn. zostały, ale do kopii tej kolekcji, przekazanej do ziarna EJB).

Trzeba uważać.

3 komentarze:

Jacek Laskowski pisze...

3.2.1 Remote Clients w specyfikacji EJB3:

"The arguments and results of the methods of the remote business interface are passed by value."

oraz dalej w 3.2.2 Local Clients:

"The arguments and results of the methods of the local business interface are passed "by reference"[1]. Enterprise beans that provide a local client view should therefore be coded to assume that the state of
any Java object that is passed as an argument or result is potentially shared by caller and callee."


...

[1] More literally, references are passed by value in the JVM: an argument variable of primitive type holds a value of that primitive type; an argument variable of a reference type hold a reference to the object.

Dlaczego JBAS 4.2.2.GA zamiast 5.0.1.GA?

Jacek

MZ pisze...

ano miałem 4.2.2 na dysku, a 5.0.1 jeszcze nie

WooKasZ pisze...

dobry wpis:) trzeba go rozpropagować ^^