<

13. EJB 2.1 jako przykład klasycznego middleware API


Materiał zawiera wprowadzenie do EJB - czym są, jakie są ich rodzaje. Oprócz tego zapoznamy się z ogólnym mechanizmem działania EJB, zwracając szczególną uwagę na obiekty pośredniczące i  interfejsy Home. Zostanie pokazany przykład aplikacji, opracowanej w wersji EJB 2.1 i wdrożonej w środowisku serwera SJAS 8.2.  W tej chwili dostępna jest specyfikacja EJB 3.0, znacznie ułatwiająca sposób programowania EJB. Jednak mechanizm działania EJB (choć w nowej wersji może być ukryty przed programistą) pozostaje ten sam i warto z nim - choćby w zarysach się zapoznać.

rWysłuchaj wprowadzenia do wykładu.


1. Komponenty EJB


Enterprise Java Bean jest komponentem programistycznym, realizującym logikę biznesową, działającym w n-warstwowym środowisku rozproszonym po stronie serwera aplikacji  i  zarządzanym przez kontener EJB.

Konieczne jest by:


Specyfikacja EJB  rozróżnia następujące typy Enterprise Java Beans:


r
Źródło: Ed Roman et al. Mastering EJB, Willey 2005
 
EJB jest komponentem przygotowanym do działania w środowisku rozproszonym - obiektem zdalnym.
Abstrahując od msg-driven beans zdalna komunikacja odbywa się za pśrednictwem RMI-IIOP.
Czyli tak jak już widzieliśmy?

r

Nie do końca! To tylko połowa drogi.


2. EJB, uslugi middleware a logika biznesowa

Potrzebne jest zapewnienie dodatkowych usług typu middleware np. związanych z bezpieczeństwem, transakcyjnością itp. W "czystym" RMI-IIOP musimy je zapewnić sami np. zawierając odpowiedni kod w metodach zdalnego obiektu (użycie API związanego z bezpieczeństwem, transakcyjnością itp.).

r

Ale główną ideą EJB jest to aby w programowaniu komponentów skupiać się na logice biznesowej.
Usługi middleware powinny być więc zapewnianie automatycznie i dla programisty komponentowego - niewidocznie.
Tutaj właśnie pojawia się rola kontenera EJB. Zapewnia on te dodatkowe usługi.
Jak? Poprzez automatyczne generowanie odpowiedniego kodu pośredniczącego:

r



Ale to wymaga - jednak!!! - aby komponenty EJB programować w bardzo specyficzny sposób - zgodny ze specyfikacją, spełniając przy tym szereg wymagań. I dodatkowo - potrzebne jest powiedzenie kontenerowi EJB jakich konkretnie usług i w jakiej konfiguracji od niego się wymaga - i stąd potrzeba deskryptorów wdrożenia. Tak przynajmniej było w wersji EJB 2.1. W wersji EJB 3.0 od wielu męczących obowiązków możemy być zwolnieni (zob. następny wykład).


3. Programowanie komponentów EJB 2.1


Sposób oprogramowania komponnetow EJB 2.1 zostanie zilustrowany na prostym przykładzie obliczania podatku od dochodów osobistych.

Klasy komponentów EJB muszą implementować odpowiednie interfejsy.
Rodzaj EJB Implementuje
session bean javax.ejb.SeesionBean
entity bean javax.ejb.EntityBean
message-driven bean javax.ejb.MessageDrivenBean
Wszystkie te interfejsy są pochodne od javax.ejb.EnterpriseBean

A zatem  implementacja sesyjnego komponentu podatkowego wygląda następująco:

package pit;

import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import java.math.*;


public class PitBean implements SessionBean {

    private BigDecimal taxRate = new BigDecimal("0.14");

    public PitBean() {
    }

    public BigDecimal taxToPay(BigDecimal income) {
        BigDecimal result = income.multiply(taxRate);

        return result.setScale(2, BigDecimal.ROUND_UP);
    }


    // Metoda wołana przy tworzeniu obiektu ziarna
    public void ejbCreate() {
    }

    // Metody interfejsu SessionBean

    public void ejbRemove() {
    }

    public void ejbActivate() {
    }

    public void ejbPassivate() {
    }

    public void setSessionContext(SessionContext sc) {
    }
}
Metod interfejsu SessionBean możemy użyć (na zasadzie call-back) do różnych działań w fazach związanych z tworzeniem, usuwaniem, aktywacją, deaktywacją obiektu, jak również do dostępu do kontekstu sesji (przekazanego w argumencie metody setSessionContext).

Ale również - tutaj właśnie - definiujemy metody inicjacji naszego obiektu biznesowego.
Metody te:
Moglibyśmy np. dodać jeszcze jedną metodę ejbCreate(BigDecimal), z argumentem - stawką podatku.

Ani metod interfejsu SessionBean, ani metod ejbCreate(..) - nie wywołujemy
Pamiętajmy:  to serwer aplikacji (kontener EJB) tworzy nasze obiekty, nie my!

Ciekawostka: nasza metoda taxToPay(...) może być wywołana zdalnie, jednak nie implementujemy żadnych zdalnych narzędzi (jak w RMI-IIOP).

I tu właśnie jest najważniejsza zmiana koncepcyjna. Polega na tym, że pomiędzy klientem a naszym komponentem-ziarnem znajduje się pośrednik - i to do niego idą wszystkie odwołania od klienta.

Pośrednikiem jest tzw. EJBObject


Ten mechanizm - przejmowanie zleceń klientów przez EJBObject, który deleguje je następnie do wykonania przez ziarno biznesowe, umożliwia kontenerowi EJB automatyczne dostarczanie usług typu middleware, dotyczących:

Skąd się bierze EJBObject? Jest generowany automatycznie przez kontener.
Ale skoro odwołania klientów muszą trafiać do niego, a dopiero za jego pośrednictwem do naszego ziarna biznesowego, to musimy powiedzieć kontenerowi, aby przy generacji EJBObject uwzględnił interfejs biznesowy naszego ziarna. Dlatego do generacji  EJBObjectu musimy dostarczyć odpowiedniego interfejsu.

Jeżeli dostęp do EJB ma być zdalny (o lokalnym dostępie powiemy kilka słów na koniec), to musimy dostarczyć zdalnego interfejsu na którym oprze się generacja EJBObject. W nim dostarczymy naszego interfejsu biznesowego. Ten zdalny interfejs (naturalnie) nazywa się w różnych narzędziach wdrożeniowych Remote.

To nie jest zwykły zdalny interfejs. Ponieważ służy do generowania EJBObject, to musi rozszerzać interfejs EJBObject.
package pit;

import javax.ejb.EJBObject;
import java.rmi.RemoteException;
import java.math.*;

public interface Pit extends EJBObject {
    public BigDecimal taxToPay(BigDecimal income) throws RemoteException;
}
Sam EJBObject rozszerza java.rmi.Remote, więc mamy zdalny obiekt, a w naszej metodzie biznesowej musimy uwzględnić możliwość zgłoszenia wyjątku RemoteException.

Ten właśnie (zdalny) interfejs (a nie jakiś implementowany w ziarnie biznesowym) służy zdalnym klientom do komunikowania się z naszym ziarnem.



Ale to jeszcze nie wszystko.
Klienci muszą uzyskać dostęp do EJBObjectu pośredniczącego w odwołaniach do naszego ziarna biznesowego.
Ten obiekt jest tworzony przez kontener: kiedy i jak?
Potrzebny jest kolejny pośrednik: fabryka obiektów EJBObject, która zajmuje się ich:
Tę fabrykę (która też jest generowana automatycznie) musimy jednak dostroić. Np. powiedzieć jak są tworzone obiekty (że są np. inicjowane Stringiem lub BigDecimal). Dlatego dostarczamy jeszcze jednego interfejsu który się nazywa Home. Ten interfejs rozszerza EJBHome (w którym znajdują się już odpowiednie metody).

package pit;

import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;


public interface PitHome extends EJBHome {
    Pit create() throws RemoteException, CreateException;
}
Tutaj dodaliśmy metodę tworzenia obiektu (bez inicjacji jakimiś argumentami) - create().
Uwagi:

Co mamy:

W zdalnym programie klienckim posługujemy się tylko interfejsami Home i Remote.


Co musi zrobić zdalny klient?
import pit.Pit;
import pit.PitHome;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import java.math.BigDecimal;


public class PitClient {
    public static void main(String[] args) {
        try {
            Context initial = new InitialContext();
            Context myEnv = (Context) initial.lookup("java:comp/env");
            Object objref = myEnv.lookup("ejb/Pit");

            PitHome home =
                (PitHome) PortableRemoteObject.narrow(objref,
                    PitHome.class);

            Pit pit = home.create();

            BigDecimal income = new BigDecimal("100000.00");
            BigDecimal tax = pit.taxToPay(income);

            System.out.println("Podatek wynosi: " + tax);

            System.exit(0);

        } catch (Exception ex) {
            System.err.println("Caught an unexpected exception!");
            ex.printStackTrace();
        }
    }
}

Obrazowo działanie całej aplikacji można przedstawić na rysunku.

r

Zatem aplikacja EJB składa się z wielu części, a wiele jej fragmentów jest automatycznie generowane przez kontener EJB.


4. Składanie aplikacji

Aplikacja dzieli się na moduły m.in. moduły  EJB oraz moduł klienta.
Moduły są pakowane do plików JAR (lub WAR - jeśli to moduł Web).
Moduły (JARY) są archiwizowane w pliku EAR (Enterprise Archive), z tym, że moduły klienckie mogą być z niego wyłączone.

Oprócz naszych klas i interfejsów, w archiwach aplikacji muszą znaleźć się odpowiednie deskryptory wdrożenia.
Są dwa typy tych deskryptorów:
Podstawowym deskryptorem jest ejb-jar.xml, który opisuje konfigurację dostępu do ziarna biznesowego, a także kwestie związane z transakcyjnością i bezpieczeństwem (oraz wiele innych).
Przykład:
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
    http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd">

<display-name>PitAppJar</display-name>
<enterprise-beans>
<session>
<display-name>PitBean</display-name>
<ejb-name>PitBean</ejb-name>
<home>pit.PitHome</home>
<remote>pit.Pit</remote>
<ejb-class>pit.PitBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Bean</transaction-type>
<security-identity>
<use-caller-identity/>
</security-identity>
</session>
</enterprise-beans>
</ejb-jar>
Oprócz tego potrzebny jest opis powiązań pomiędzy zasobami a nazwami JNDI (np. w naszym przykładzie pomiedzy odwołaniem ejb/Ref a konkretnym ziarnem biznesowym).
Po stronie klienta (w archiwum klienta) musimy w tym celu dostarczyć pliku wdrożeniowego application-client.xml:

<?xml version="1.0" encoding="UTF-8"?>
<application-client xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.4" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/application-client_1_4.xsd">
<display-name>PitAppClient</display-name>
<ejb-ref>
<ejb-ref-name>ejb/Pit</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<home>pit.PitHome</home>
<remote>pit.Pit</remote>
</ejb-ref>
</application-client>

Różne serwery aplikacji mogą stosować swoje dodatkowe reguły. Dlatego takich informacji dostarczamy w deskryptorach wdrożenia dla specyficznych serwerów np. dla serwera Sun Application Server w pliku sun-ejb-jar.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 8.1 EJB 2.1//EN" "http://www.sun.com/software/appserver/dtds/sun-ejb-jar_2_1-1.dtd">

<sun-ejb-jar>
  <enterprise-beans>
    <name>PitAppJar</name>
    <ejb>
      <ejb-name>PitBean</ejb-name>
      <jndi-name>PitBean</jndi-name>
    </ejb>
  </enterprise-beans>
</sun-ejb-jar>

oraz (po stronie klienta)  sun-application-client.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-application-client PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 8.1 Application Client 1.4//EN" "http://www.sun.com/software/appserver/dtds/sun-application-client_1_4-1.dtd">
<sun-application-client>
<ejb-ref>
<ejb-ref-name>ejb/Pit</ejb-ref-name>
<jndi-name>PitBean</jndi-name>
</ejb-ref>
</sun-application-client>

Po zdefiniowaniu deskryptorów wdrożenia oraz skompilowaniu wszystkich klas, spakowaniu ich w moduły (JARY) nasza aplikacja składa się z pliku EAR (PitApp.ear) i modułu klienckiego PitAppClient.jar.
Ich strukturę pokazują rysunki.
r
r









5. Wdrażanie aplikacji


Moduł EJB wdrażamy na serwerze i uruchamiamy aplikację.

Moduł kliencki służy do zdalnego dostępu. Jak widzieliśmy jest to zwykła wolnostojąca aplikacja.
Można też dostarczyć klientów Web (czyli aplikacji Web, które pozwalają na działanie za pośrednictwem dynamicznie generowanych stron HTML. Ale o tym sporo było w ramach "Metod programowania" i teraz to pominiemy.

Jak działa klient?
I tutaj sprawa nie jest prosta. Jeśli nie chcemy grzebać we "wnętrznościach" specyfikacji serwera aplikacji, klienta powinniśmy uruchamiać w tzw. kontenerze klienta.
Kontener ten jest specjalnie skonfigurowany w taki sposób, by bezproblemowo łączyć się z serwisami nazw czy JMS, korzysta z szeregu klas J2EE, wykorzystuje deskryptory dostarczone w pliku JAR.
W środowisku serwera Sun App Server do uruchamiania klientów w kontenerze klientów służy plik wsadowy appclient.bat.


Na szczęście tworzenie deskryptorów i wdrażanie aplikacji jest znacznie ułatwione dzięki narzędziom wdrożeniowym.

W środowisku serwera Sun mamy do dyspozycji asant (wersję anta) oraz deploytool (w wersjach wcześniejszych od 9, wdrażanie aplikacji w środowisku serwera SJAS 9 zostanie przdstawione w natępnym wykładzie).
Dodatkowo dostarczone są pliki build.xml i properties.xml z definicjami zadań asanta.

Nasz projekt aplikacji powinniśmy umieścić w następującej strukturze katalogowej:
src - źródła,
web - elementy webowe (jsp, strony html)


A. Aplikację kompilujemy za pomocą asant build.

Powstaje następująca struktura katalogowa:
r

B. Uruchamiamy serwer aplikacji a następnie deploytool.

C. Tworzymy nową aplikację: File -> New -> Application i wybierając nazwę pliku EAR i nazwę aplikacji (np. PitApp).

r


D. W aplikacji tworzymy komponent EJB: File -> New -> Enterprise Bean i dostarczamy odpowiednich informacji w kolejnych krokach "New Enterprise Bean Wizard":

d1) ustawienia ogólne

r

d2) "Edit contents" - zawartość JARu

r

d3) ustalamy nazwy klas i interfejsów - to łatwe bo wybieramy z list kombo, w prostych przypadkach wybór jest jeden

r



E. Tworzymy JAR kliencki - dla ułatwienia w tej samej aplikacji (File -> New -> Apllication Client)

r

- wybierając "Edit Contents" specyfikujemy zawartość JARu klienta:

r

- a następnie co jest klasą główną:

r


F. W ustawieniach klienta specyfikujemy powiązania pomiędzy nazwami użytymi w programie (ejb/Pit) a zasobami (do czego się to odwołuje):

r

r


Na koniec możemy sprawdzić, czy powiązania JNDI są wystarczające (być może będziemy mieli więcej klientów itp.) przyciskiem "Sun Specific Options" przy wybranej na liście z lewej strony naszej aplikacji.

r


Wdrażamy aplikacje wybierając  menu Tools -> Deploy i zaznaczając, że ma być zwrócony oddzielny jar kliencki:

r


Aplikacja działa na serwerze, a jej klienta możemy uruchomić w następujący sposób:



D:\>appclient -client PitAppClient.jar
Podatek wynosi: 14000.00

D:\>


Na koniec wzmianka o lokalnych interfejsach.
Zdalny dostęp jest czasochłonny (ze względu na potzrebę serializacji). Jeżeli chcemy korzystać z  ziaren lokalnie (i szybciej), to dostarczamy lokalnego interfejsu dla EJBObject i dla Home.

6. Podsumowanie


Zapoznaliśmy się z:


7. Zadania



Zadanie 1

Na wzór aplikacji Pit stworzyć podobny program - np. kalkulator walutowy.

Zadanie 2

Porównać sposoby wdrażanie aplikacji EJB 2.1 w środowiskach serwerów SJAS i JBoss.

Zadanie 3

Korzystając z wykładu o JMS i dodatkowych informacji z J2EE Tutorial  przygotować przykład aplikacji, używającej  Message Driven Bean.