29 stycznia 2008

EJB 3.0 - Rozdział 12.2 Cykl rozwojowy interceptora

Pora wrócić do specyfikacji EJB3 - JSR 220: Enterprise JavaBeansTM,Version 3.0 EJB Core Contracts and Requirements. Tym razem pod młotek pójdzie rozdział 12 dotyczący interceptorów. Pisałem już o nich wcześniej - Elementy programowania aspektowego w EJB 3.0 - część 1 oraz Elementy programowania aspektowego w EJB 3.0 - część 2: Interceptory biznesowe, ale wciąż odczuwam niedosyt ich poznania.

Zaczniemy od rozdziału 12.2 Cykl rozwojowy interceptora.

Dla zobrazowania tematu wykorzystamy ziarno EJB - bankomat. Najpierw prezentacja jego interfejsu biznesowego - pl.jaceklaskowski.ejb3.interceptors.Bankomat.

package pl.jaceklaskowski.ejb3.interceptors;

public interface Bankomat {

void wyplac(int kwota);

}

Kolejnym elementem ziarna jest implementacja interfejsu biznesowego w postaci klasy pl.jaceklaskowski.ejb3.interceptors.BankomatBean.

package pl.jaceklaskowski.ejb3.interceptors;

import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
import javax.jws.WebService;

@Stateless(name="bankomat")
@WebService(serviceName="bankomat")
@Interceptors(BankomatInterceptor.class)
public class BankomatBean implements Bankomat {

Logger logger = Logger.getLogger(BankomatBean.class.getName());

public BankomatBean() {
logger.info("Ziarno EJB utworzone");
}

public void wyplac(int kwota) {
logger.info("Wyplacam kwote " + kwota);
}

}

Do dyspozycji mamy związany z nim interceptor - BankomatInterceptor.

package pl.jaceklaskowski.ejb3.interceptors;

import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.ejb.EJBContext;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

public class BankomatInterceptor {

Logger logger = Logger.getLogger(BankomatInterceptor.class.getName());

@PersistenceContext
EntityManager em;

@Resource(name="dozwolonaKwota")
int dozwolonaKwota = -1;

@Resource
EJBContext ejbContext;

public BankomatInterceptor() {
logger.info("Interceptor utworzony");
logger.info("\tem == null ? " + (em == null));
logger.info("\tdozwolonaKwota == -1 ? " + (dozwolonaKwota == -1));
}

@PostConstruct
void postConstructBean( InvocationContext ic) {
logger.info("Wywolanie @PostConstruct dla " + ic.getTarget().getClass().getSimpleName());
logger.info("\tem == null ? " + (em == null));
logger.info("\tdozwolonaKwota == -1 ? " + (dozwolonaKwota == -1));
}

@PreDestroy
void preDestroyBean( InvocationContext ic) {
logger.info("Wywolanie @PreDestroy dla " + ic.getTarget().getClass().getSimpleName());
}

@AroundInvoke
void aroundInvokeBusinessMethod( InvocationContext ic) throws Exception {
String metoda = ic.getTarget().getClass().getSimpleName() + "." + ic.getMethod().getName() + "()";
logger.info("Przed wywolaniem metody biznesowej " + metoda);
try {
int kwota = (Integer)ic.getParameters()[0];
if (kwota < dozwolonaKwota) {
String komunikat = String.format("Wyplata dozwolona - kwota wyplacana %s mniejsza niz dozwolona %s", kwota, dozwolonaKwota);
logger.info(komunikat);

ic.proceed();

// Opcjonalnie: odnotuj wypłatę za pomocą zarządcy encji - em
} else {
String komunikat = String.format("Wyplata zabroniona - kwota wyplacana %s wieksza niz dozwolona %s", kwota, dozwolonaKwota);
logger.info(komunikat);
}
} finally {
logger.info("Po wywolaniu metody biznesowej " + metoda);
}
}
}

Pomocniczy persistence.xml dla demonstracji wstrzeliwania zależności:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="bazadanych">
<jta-data-source>jdbc/__default</jta-data-source>
</persistence-unit>
</persistence>

oraz deskryptor wdrożenia ejb-jar.xml, w którym definiuję zmienną środowiskową dla interceptora:

<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
version="3.0">
<interceptors>
<interceptor>
<interceptor-class>pl.jaceklaskowski.ejb3.interceptors.BankomatInterceptor</interceptor-class>
<env-entry>
<env-entry-name>dozwolonaKwota</env-entry-name>
<env-entry-type>java.lang.Integer</env-entry-type>
<env-entry-value>20</env-entry-value>
</env-entry>
</interceptor>
</interceptors>
</ejb-jar>

Egzemplarz interceptora istnieje tak długo, jak długo istnieje egzemplarz ziarna EJB, z którym jest związany. Podczas tworzenia ziarna, tworzone są związane interceptory. Ulegają one zniszczeniu w momencie usuwania ziarna z systemu. W przypadku interceptorów związanych ze stanowymi ziarnami sesyjnymi, egzemplarze interceptorów podlegają tym samym stadiom pasywacji i aktywacji. Więcej w rozdziałach o stanowych ziarnach sesyjnych (4.4 oraz 4.5.1 i 5.5).

Podczas wywołania ziarna bankomat mamy następującą kolejność komunikatów (dla kwoty == 5):

Ziarno EJB utworzone
Interceptor utworzony
em == null ? true
dozwolonaKwota == -1 ? true
Wywolanie @PostConstruct dla BankomatBean
em == null ? false
dozwolonaKwota == -1 ? false
Przed wywolaniem metody biznesowej BankomatBean.wyplac()
Wyplata dozwolona - kwota wyplacana 5 mniejsza niz dozwolona 20
Wyplacam kwote 5
Po wywolaniu metody biznesowej BankomatBean.wyplac()
Wywolanie @PreDestroy dla BankomatBean // wyłącznie podczas Undeploy and Deploy w NetBeans

Zarówno egzemplarz interceptora oraz ziarna są tworzone lub aktywowane zanim wywołane zostaną metody zwrotne @PostConstruct oraz @PostActivate. Dla metod zwrotnych @PreDestroy oraz @PrePassivate jest podobnie z tym, że wywoływane są zanim nastąpi etap zniszczenia lub pasywacji egzemplarza ziarna lub interceptora.

Egzemplarz interceptora może przechowywać stan. Egzemplarz interceptora podlega mechanizmowi wstrzeliwania zależności, który podczas tworzenia interceptora wstrzeliwuje zależności korzystając z przestrzeni nazw związanego ziarna EJB. Metoda przechwytująca @PostConstruct wywoływana jest po zakończeniu wstrzeliwania zależności egzemplarza ziarna EJB oraz wszystkich skojarzonych interceptorów.

Interceptory mogą korzystać z usług JNDI, JDBC, JMS, JPA oraz wywoływać inne ziarna EJB.

Interceptory dzielą przestrzeń nazw ziarna, z którym są skojarzone. Adnotacje i elementy deskryptora ejb-jar.xml dotyczące mechanizmu wstrzeliwania zależności lub bezpośrednie wyszukiwania JNDI dotyczą wyłącznie tej przestrzeni.

Egzemplarz EJBContext może być wstrzelony do klasy interceptora. Interceptor może skorzystać z niego do przeszukiwania przestrzeni nazw JNDI ziarna.

Użycie rozszerzonego kontekstu trwałego jest jedynie wspierane dla interceptorów związanych ze stanowymi ziarnami sesyjnymi.

p.s. Nie zapomnieliście o konkursie Blog Roku 2007? Coś słabo idzie nam z tym głosowaniem i wypadam na 16. miejscu z 2-ma głosami w III etapie (40 głosów w II etapie) (!) Niedobrze. Pora Was rozruszać!

5 komentarzy:

  1. Jakos mi te polskie nazwy (komunikat, bankomat) nie pasuja w otoczeniu invoke, proceed, get, set, format..
    Albo tak, albo siak..

    Ja bym jednak uzyl wszedzie angielskich w celu uzyskania spojnosci.
    Nie wspominam o komentarzach, aczkolwiek.. mlodzi adepci Javy powinni sie powoli szkolic w czytaniu angielskiego.

    OdpowiedzUsuń
  2. Tam zaraz albo tak, albo siak. A dlaczego dla urozmaicenia, nie stosować i tak i siak? To, że pewna część nomenklatury jest obowiązkowa i narzucana przez specyfikację, nie oznacza, że od razu wszystko musi być po angielsku. Powiem więcej, z mojego punktu widzenia użycie polskich odpowiedników, jakkolwiek czasami zabawne, przyjmuje się i jest często bardzo dobrze odbierane. A to, że są konserwatywni odbiorcy - no cóż, wszystkim nie dogodzisz ;-)

    Jacek

    OdpowiedzUsuń
  3. Usmialem sie kolejna czescia: if (kwota < dozwolonaKwota). Haha.
    Jak to po prostu na glos czytam, to jest to zabawne. Przynajmniej tyle tego dobrego.
    Dalej masz zreszta "BankomatInterceptor" - zgrozo!
    Wazne, oby Ci sie dobrze pisalo :)

    No dobrze; do tematu. Przeczytalem z zainteresowaniem, jako, ze na aspektach sie znam, a w EJB jestem laikiem (wiec wybacz tutaj moja nomenklature..)

    Staralem sie zrozumiec jaka jest roznica pomiedzy "pospolitym" AOP zaaplikowanego po prostu do klasy takiego "bankomatu" - czyli w koncu do obiektow tej klasy - a interceptorami w wykonaniu tego co przedstawiasz.

    Pierwsza, to jest oczywiscie sposob dzialania: AOP trzeba uzyc poprzez pre-kompilacje, czy kompilacje aspectj, czy podmiane classloadera.
    Tutaj rozumiem, sa do tego ladne adnotacje i wszystko dziala bez zadnych dodatkowych udziwnien AOP. Wiec dostajemy gotowa paczke API i mozemy z niej korzystac.


    Druga rzecz: obiekty EJB, jak rozumiem moga byc na zdalne serwery "przerzucane", i ich stan bedzie prawidlowo propagowany.
    W zasadzie zwykle AOP dzialoby w "zdalnym" przypadku na wszystkich zdalnych instancjach obiektu identycznie, oprocz szczegolu, ze sam "interceptor" moze rowniez posiadac stan (o czym nadmieniasz). Zwykle AOP tu nic nie da i jak rozumiem (dobrze?) EJB#interceptor, daje to, ze jego stan rowniez jest synchronizowany na zdalne serwery razem z samym obiektem, na ktorym dziala. (Zastosowanie tego, to oczywiscie inna bajka - tez ciekawa).
    Czyli uzywajac EJB#interceptor mamy taki "rozproszony aspekt"?..

    Trzecia sprawa: nazwy metod klasy interceptora.
    Jako, ze wystepuja tu specjalne adnotacje na oznaczenie czasu uruchomienia metody (@Pre*, @Post*, @Around*..), to czy nazwa metody nie powinna byc dowolna -- czyli w Twym przypadku Polska ;-) ?

    OdpowiedzUsuń
  4. Cześć Tomek,

    Właśnie o to mi chodziło, aby publikować moje spostrzeżenia/relacje publicznie i oczekując konfrontacji z postrzeganiem rozwiązań przez innych użytkowników, tak aby pojawiające się propozycje mogły i mi posłużyć (a że przy okazji i nowicjusze w temacie się podszkolą, tym lepiej dla wszystkich, bo będzie więcej krytycznych uwag).

    Wracając do Twojego komentarza, zastanawiam się co śmiesznego jest w konstrukcji if (kwota < dozwolonaKwota)? Możemy sobie wyobrazić zaaplikowanie ograniczenia do już działającego ziarna bez modyfikacji jego kodu i włączać/wyłączać dynamicznie (tzn. dynamicznie = bez ingerencji w kod źródłowy ziarna). Dodatkowo, jak zresztą nadmieniasz, idą za tym inne korzyści jak korzystanie ze środowiska EJB wliczając w to dostęp do wszystkich zasobów, z których korzysta ziarno, z którym dany interceptor jest związany.

    Za mało pracowałem z "pospolitym" AOP, aby próbować go porównać. Na spotkaniu Warszawa JUG, Waldi (=Waldemar Kot) poruszył ten temat i ostatecznie "wyciął w pień" wszystkie udoskonalenia EJB w zakresie AOP jako ograniczające i przesłaniające prawdziwe możliwości AOP w wydaniu non-EJB, np. aspectj, co oczywiście w połączeniu z Springiem (w/g Waldiego) może pretendować do miana najlepszej paczki programistycznej XXI ;-) Gdyby tak poruszyć ten temat na pl.comp.lang.java zawiązałaby się ciekawa dyskusja (zapewne z Waldim na pierwszym planie).

    Jeśli dobrze rozumiem, to "tradycyjne" AOP mamy wykorzystane np. w wykonaniu JPA, które podmienia klasy encji na własne w celu śledzenia zmian i odpowiednim zareagowaniu, aby utrzymać spójność danych między aplikacją a bazą. Nie piszę tutaj o interceptorach w encjach, ale sposobie zarządzania encjami przez dostawców JPA. Podsumowując myśl: nie trzeba prekompilacji, czy podmiany classloadera, ale wystarczy włączyć własną instrumentację podczas ładowania klas przez zarządcę klas. I w EJB3, i AOP nie zauważam konieczności wykonania niczego szczególnego (co zamierzam sprawdzić właśnie na przykładzie Springa niedługo).

    Odnośnie drugiej rzeczy to specyfikacja nie opisuje sposobu propagowania zmian w środowisku klastrowym (zdalne serwery), ale gwarantuje spójność działania ziaren i całego ich środowiska w ramach serwera. Pytanie, gdzie kończy się granica serwera i odpowiedzi na to pytanie należałoby szukać w dokumentacji producenta serwera.

    Odnośnie to czy nazwa metody nie powinna byc dowolna -- czyli w Twym przypadku Polska. Jest dowolna - szczypta angielskich nazw, które potraktowałem jako bliższe tematowi, jest "instancją" dowolnej nazwy metody ;-)

    Jacek

    OdpowiedzUsuń
  5. Z tego co piszesz, to faktycznie slabo wyglada api AOP w EJB. Jesli dodatkowo stan "aspektu" nie jest zdalnie propagowany/synchronizowany, to juz zostaje tylko jedna zaleta: jako-takie-AOP w specyfikacji EJB i brak potrzeby uzycia dodatkowych narzedzi..

    Sam warunek biznesowy "if (kwota < dozwolonaKwota)" jest bardzo dobry do przykladu; bo ladnie pokazuje wlasnie zalety AOP: mozliwosc nalozenia dodatkowego kodu na jakis bazowy kod - bez faktycznego umieszczania blokow tego kodu w danym miejscu.
    Oczywiscie mi tylko chodzilo o polskosc tego wyrazenia...if (amount < maxValidAmount) EOT

    Aczkolwiek w praktyce rzadko kto zdecyduje sie na taki sposob walidowania metod biznesowych. W tym wypadku niestety dla praktycznej wiekszosci ludzi zaciemniamy biznesowy kod. Bo jak ktos ma wiedziec, ze gdzies "siedzi" dodatkowy interceptor (Eclipse moze i ma wsparcie)..

    Wydaje mi sie, ze o ile wogole ktos zastosuje AOP, to do bardziej "technicznych" wymagan (chocby "tracing"), no ale.. moze ktos sie tym pochwali.

    OdpowiedzUsuń