poniedziałek, 26 stycznia 2009

gry i zabawy ze szklaną rybką

Zdarzyło się tak, że w nowym 2009 roku wpadła w moje śliskie łapska niezła cegła.
Mastering Enterprise JavaBeans 3.0. Ponad 700 stron lektury. Jak przebrnę przez wszystko, postaram się zamieścić recenzję. Mam nadzieję, że wyrobię się przed rokiem pańskim 2010.
Mastering Enterprise JavaBeans 3.0 traktuje całkiem szczegółowo o EJB 3.0. Czas zatem rozpocząć wycieczkę w tamte rejony. Wycieczki są fajne, wszak podróże kształcą. Jednak wycieczki w pojedynkę bywają nudne. Na szczęście udało mi się namówić do wyprawy serwer aplikacyjny GlassFish. We dwójkę raźniej.

Zacząłem od instalacji mojego towarzysza. Pobrałem serwer GlassFish 2. Co ciekawe, do instalacji nie wystarczy odpalenie pobranego pliku glassfish-installer-v2.1-b60e-linux.jar. Aby w pełni zainstalować serwer, należy jeszcze uruchomić antem znajdujący się w jego katalogu głównym plik setup.xml. Czyli:

  
java -jar glassfish-installer-v2.1-b60e-linux.jar
cd glassfish
ant -f setup.xml


Dopiero teraz możemy się cieszyć w pełni działającym serwerem Java EE.
Żeby go uruchomić, wchodzę do katalogu bin GlassFisha i startuję domenę domain1.
 
./asadmin start-domain domain1
Starting Domain domain1, please wait.
Log redirected to /opt/glassfish/domains/domain1/logs/server.log.
Redirecting output to /opt/glassfish/domains/domain1/logs/server.log
Domain domain1 is ready to receive client requests. Additional services are being started in background.
Domain [domain1] is running [Sun Java System Application Server 9.1_02 (build b04-fcs)] with its configuration and logs at: [/opt/glassfish/domains].
Admin Console is available at [http://localhost:4848].
Use the same port [4848] for "asadmin" commands.
User web applications are available at these URLs:
[http://localhost:8080 https://localhost:8181 ].
Following web-contexts are available:
[/web1 /__wstx-services ].
Standard JMX Clients (like JConsole) can connect to JMXServiceURL:
[service:jmx:rmi:///jndi/rmi://jt-laptop:8686/jmxrmi] for domain management purposes.
Domain listens on at least following ports for connections:
[8080 8181 4848 3700 3820 3920 8686 ].
Domain does not support application server clusters and other standalone instances.

Wchodzę na stronę
http://localhost:8080


Wszystko ładnie działa.

W EJB 3.0, mamy 3.0 rodzaje ziaren. Ziarna sesyjne, ziarna sterowane wiadomością i ziarna encyjne, które są, ale jakby ich nie było... Dziś zajmę się tymi pierwszymi i to tylko częściowo. Ziarna sesyjne bowiem występują w dwóch odmianach, stanowej i bezstanowej. Zarówno jedne, jak i drugie zazwyczaj wykonują tak zwaną logikę biznesową. Robią to niby wydajnie, skalowalnie, och i ach, ale moim skromnym zdaniem siła EJB ukryta jest tutaj zupełnie gdzie indziej. Otóż w sposób poniekąd przezroczysty dla programisty pozwalają na zarówno lokalne jak i zdalne wywoływanie usług. Możemy je wykonywać w ramach jednej maszyny wirtualnej (np. aplikacja webowa) jak i z osobnych (gruby klient). Zobaczmy jak to działa.

Żeby nie było za nudno, stworzę 2 aplikacje EJB i odpalę je w różnych domenach GlassFisha. Następnie "grubym" klientem wykonam metody jednego i drugiego ziarna. Do dzieła.

EJB 3.0 uprościło się znacznie od zamierzchłych czasów wersji 2.x. Żeby stworzyć ziarno sesyjne i jego zdalny interfejs, potrzebuję tylko... interfejs i jego implementację.

  
interfejs Hello.java


public interface Hello {

public String hello();
}

i jego implementacja

@Stateless
@Remote(Hello.class)
public class HelloBean implements Hello {

public String hello() {
return "domain1";
}

}


@Stateless oznacza, że jest to ziarno bezstanowe
@Remote(Hello.class) informuje kontener EJB (czyli w moim przypadku GlassFisha), że jest to imlpementacja zdalnego interfejsu Hello.

No i to już wszystko.

Powyższą aplikację EJB buduję Mavenem 2.
Oto pom.xml:
  
<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/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>pl.matt</groupId>
<artifactId>ejb3-slsb</artifactId>
<version>1.0</version>
<packaging>ejb</packaging>

<properties>
<JAVA_HOME>/usr/lib/jvm/java-6-sun</JAVA_HOME>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-ejb_3.0_spec</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<finalName>ejb3-slsb</finalName>
<directory>/tmp/mvn-target/</directory>
<resources>
<resource>
<targetPath>/META-INF</targetPath>
<filtering>false</filtering>
<directory>${basedir}/src/main/resources</directory>
<includes>
<include>ejb-jar.xml</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ejb-plugin</artifactId>
<configuration>
<ejbVersion>3.0</ejbVersion>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<verbose>true</verbose>
<fork>true</fork>
<executable>${JAVA_HOME}/bin/javac</executable>
<compilerVersion>1.6</compilerVersion>
<source>1.6</source>
<target>1.6</target>
<debug>true</debug>
<optimize>false</optimize>
</configuration>
</plugin>
</plugins>
</build>
</project>

Zwracam uwagę na <packaging>ejb</packaging> dzięki czemu Maven zbuduje aplikację EJB.

Tworzę również drugą aplikację EJB, która odrobinę różni się od pierwszej implementacją interfejsu Hello.class


@Stateless
@Remote(Hello.class)
public class HelloBean implements Hello {

public String hello() {
return "domain2";
}

}

Aplikację w tej wersji, wdrożę na osobnej domenie serwera GlassFish.

GlassFish po instalacji skonfigurowany jest z domeną domain1. Nową, o nazwie domain2 tworzę poleceniem, które wykonuje w katalogu bin GlassFIsha.
  
./asadmin create-domain --adminport 4849 domain2

Please enter the admin user name>admin
Please enter the admin password>
Please enter the admin password again>
Please enter the master password [Enter to accept the default]:>
Please enter the master password again [Enter to accept the default]:>
Using port 4849 for Admin.
Default port 8080 for HTTP Instance is in use. Using 47039
Default port 7676 for JMS is in use. Using 59523
Default port 3700 for IIOP is in use. Using 38742
Default port 8181 for HTTP_SSL is in use. Using 34802
Default port 3820 for IIOP_SSL is in use. Using 36960
Default port 3920 for IIOP_MUTUALAUTH is in use. Using 48963
Default port 8686 for JMX_ADMIN is in use. Using 55733
Domain being created with profile:developer, as specified by variable AS_ADMIN_PROFILE in configuration file.
The file in given locale [pl_PL] at: [/opt/glassfish/lib/install/templates/locales/pl_PL/index.html] could not be found. Using default (en_US) index.html instead.
Security Store uses: JKS
Domain domain2 created.

Podaję kilka haseł i tyle.
Port konsoli administracyjnej to 4849. Port IIOP, którego używa EJB to 38742.

Czas wdrożyć aplikacje w obu domenach. Wchodzę więc do panelu administracyjnego, dla domeny domain1 będzie to adres:
http://localhost:4848/

dla domeny dmain2
http://localhost:4849/

Loguję się jako administrator. Domyślnie jest to użytkonwik o nazwie admin i haśle adminadmin.



wchodzę w pozycję bocznego menu Applications/EJB Modules i klikam ikonkę deploy.

Jako Packaged file to be uploaded to the server wskazuję plik jar ejb3-slsb.jar. Plik ten utworzył Maven w katalogu /tmp/mvn-target/
Teraz już tylko OK. Aplikacje w różnych wersjach osadzam w obu domenach.

Czas na klienta EJB, który wywoła metody z ziaren zdnajdujących się w różnych domenach. To już zwykła aplikacja w postaci jednej klasy.

public class HelloClients {

public static void main(String[] args) throws Exception {
Context ctx = new InitialContext();
Hello hello = (Hello) ctx.lookup("pl.matt.interfaces.Hello");

System.out.println(hello.hello());
}
}

Referencję do ziarna EJB otrzymujemy poprzez wywołanie metody lookup na obiekcie Context. Domyślnie konstruktor new InitialContext() będzie korzystał z serwera o adresie localhost i porcie 3700 (w tym przypadku będzie to domena domain1). Aby to zmienić i skorzystać z ziarna w domenie domain2, trzeba skorzystać z konstruktora new InitialContext(Properties properties).

public class HelloClients {

public static void main(String[] args) throws Exception {
Context ctx = new InitialContext();
Hello hello = (Hello) ctx.lookup("pl.matt.interfaces.Hello");

System.out.println(hello.hello());

Properties properties = new Properties();
properties.setProperty("org.omg.CORBA.ORBInitialHost", "localhost");
properties.setProperty("org.omg.CORBA.ORBInitialPort", "38742");

Context context = new InitialContext(properties);
hello = (Hello) context.lookup("pl.matt.interfaces.Hello");
System.out.println(hello.hello());

}

}

podając odpowiednie ustawienia właściwości org.omg.CORBA.ORBInitialHost i org.omg.CORBA.ORBInitialPort uzyskuję dostęp do ziarna EJB z domeny domain2.

Aplikacja wymaga w swojej ścieżce klas plików appserv-rt.jar oraz j2ee.jar, które znajdują się w podkatalogu lib GlassFisha.

Do kompletu brakuje jeszcze pliku pom.xml aplikacji klienckiej:

<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/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>pl.matt</groupId>
<artifactId>ejb3-client</artifactId>
<version>1.0</version>
<packaging>jar</packaging>

<properties>
<JAVA_HOME>/usr/lib/jvm/java-6-sun</JAVA_HOME>
</properties>

<dependencies>
<dependency>
<groupId>pl.matt</groupId>
<artifactId>ejb3-slsb</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-ejb_3.0_spec</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<finalName>ejb3-client</finalName>
<directory>/tmp/mvn-target/</directory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<verbose>true</verbose>
<fork>true</fork>
<executable>${JAVA_HOME}/bin/javac</executable>
<compilerVersion>1.6</compilerVersion>
<source>1.6</source>
<target>1.6</target>
<debug>true</debug>
<optimize>false</optimize>
</configuration>
</plugin>
</plugins>
</build>
</project>

zależność

<dependency>
<groupId>pl.matt</groupId>
<artifactId>ejb3-slsb</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>

wprowadza widoczność zdalnego interfejsu Hello w aplikacji klienckiej.

Czas aplikację uruchomić.
Na konsoli wypisuje się odpowiednio:

domain1
domain2

Czyli odpalone zostało ziarno EJB w różnych domenach. Pełen sukces.



Natknąłem się na informacje, że ziarna EJB 3 można uruchamiać klientami napisanymi w technologii EJB 2. Udało się wam to osiągnąć?

4 komentarze:

pawelstawicki pisze...

Dzięki za interesujący wpis. Drobna uwaga. Raz robisz:
ctx.lookup("pl.matt.interfaces.Hello");
a raz:
ctx.lookup("pl.com.javatech.interfaces.Hello");

Domyślam się że to błąd spowodowany zmiana pakietu. Tym niemniej chętnie dowiedziałbym się, skąd serwer wie, że właśnie w takim kontekście ma wystawić ziarno? Czy wystarczy mu do tego adnotacja @Stateless, i już wie że to jest ziarno i że trzeba je wystawić w kontekście "nazwa_pakietu.nazwa_interfejsu"?

Pozdrawiam

WooKasZ pisze...

świetny wpis ! :-)

Dodam od siebie tylko, że adnotacje @Remote można dodać do interfejsu zamiast do konkretnego ziarna, zawsze linijka kodu mniej, gdy tworzymy kilka ziaren opartych o ten sam interfejs ;-P

Jacek Laskowski pisze...

Świetnie napisane - zwięźle (czego mi zwykle brakuje).

Ale mam również kilka komentarzy:

1. @Remote(Hello.class) może być po prostu @Remote, bo wszystkie (jeden w tym przypadku) realizowane interfejsy są zdalne.

2. Prawie, bowiem aplikacja EJB wymaga również deskryptora EJB, pliku ejb-jar.xml - tutaj przesadziłeś! Właśnie to była ta świeżość EJB3 - zero XMLi, albo ich minimalna liczba. Akapit do usunięcia (a Ty do książki :P)

3. Dlaczego stworzyłeś całą tą konfigurację maven-compiler-plugin ręcznie?! Wystarczy zadeklarować 1.6 i tyle. Tak po prawdzie, to 1.5 jest wspierane przez Java EE 5.

@WooKasZ - preferuję wskazanie typu interfejsu w implementacji, bo w ten sposób możesz wskazać jawnie co i jak będzie realizowane w klasie realizującej - implementacji.

MZ pisze...

dzięki za komentarze. Błędy poprawione, a ja wracam do czytania...