26 grudnia 2006

Dokończenie rozdziału 3 specyfikacji EJB3 - 3.4 Interceptors

Temat interceptorów pozostawiłem sobie na powrót do Warszawy z wyjazdu wigilijnego do rodziny. Wigilia się skończyła, niedługo sylwester. Czas zabawy, więc korzystając z trzeźwego spojrzenia, rozczytuję się w specyfikacji EJB3. Na dziś przypadło mi doczytanie sekcji 3.4 Interceptors.

Termin interceptor pojawia się po raz pierwszy w specyfikacji EJB właśnie w wydaniu 3. Nie jest to jednak pojęcie nowe w świecie programowania. Idea interceptorów została zapożyczona z podejścia programistycznego opartego o aspekty z Aspect-Oriented Programming (AOP). Nie chciałbym rozpisywać się o aspektach, które same w sobie wymagałyby osobnej pozycji w moim dzienniku, ale zrozumienie aspektów to zrozumienie interceptorów, więc kilka słów znacznie pomoże w temacie. Po więcej informacji zapraszam do lektury The AspectJ 5 Development Kit Developer's Notebook.

Aspekt jest pewną funkcjonalnością, która niekoniecznie związana jest z działaniem biznesowym aplikacji. Modelowanie aplikacji oparte o aspekty wprowadza dodatkowy element modularyzacji dostarczając zestaw rozszerzeń (coś na kształt wtyczek) do podstawowej wersji aplikacji. Najczęstszymi przykładami aspektów są funkcjonalności rozbudowujące aplikację (a raczej jej funkcje biznesowe) o funkcje audytowe (ang. logging), bezpieczeństwo, czy obsługę transakcji. Jakkolwiek dwa ostatnie są dostarczane przez serwer aplikacyjny to pierwszy z nich - funkcje audytu - nie istnieje (lub innymi słowy, nie jest częścią specyfikacji Java EE, a jedynie serwera aplikacyjnego, stąd specyficzne dla niego i potraktowane przeze mnie jako nieistniejące).

Po lekturze rozdziału 3.4 Interceptors interceptory wydają się być dla mnie okrojonymi aspektami, tj. możliwości aspektów w dojrzałych szkieletach programistycznych AOP (np. AspectJ) znacznie przewyższają możliwości interceptorów w EJB3.

Czym zatem jest interceptor w sensie specyfikacji EJB3?

Interceptor jest metodą, która przechwytuje wywołanie metody biznesowej (metody z interfejsu biznesowego) lub zdarzenia zwrotnego związanego z etapem życia komponentu (bardzo podobne do nasłuchiwaczy - ang. listeners). Interceptor może być zdefiniowany na klasie komponentu bądź we własnej, dedykowanej klasie interceptora związanej z komponentem. Klasa interceptora to klasa zawierająca interceptory. Zabronione jest, aby klasa interceptora była jednocześnie komponentem EJB. Pojęcie interceptorów związane jest wyłącznie z komponentami sesyjnymi i sterowanymi komunikatami, i nie występuje dla komponentów encyjnych. Istnieje możliwość zdefiniowania interceptora metod biznesowych do wszystkich metod biznesowych komponentu bądź dla dowolnego ich podzbioru.

Klasa interceptora związana jest z klasą komponentu EJB za pomocą annotacji @Interceptors lub w deskryptorze instalacji - ejb-jar.xml. Pojawia się pojęcie domyślnych interceptorów, czyli interceptorów, które związane są ze wszystkimi komponentami sesyjnymi i sterowanych komunikatami zdefiniowanymi w deskryptorze instalacji wraz z definicją samych interceptorów. Brzmi bardzo zagmatwanie i oznaczyłem do dalszego sprawdzenia.

Nie ma ograniczenia dla liczby interceptorów zdefiniowanych dla pojedyńczej klasy komponentu EJB. Kolejność wykonywania interceptorów wyznaczana jest przez kolejność ich deklaracji w klasie komponentu i deskryptorze instalacji (jak ostatecznie tworzona jest kolejność pozostaje do sprawdzenia, choć wydaje się, że najpierw te zadeklarowane przez annotacje, a później przez deskryptor instalacji - choć może...ech..zostawiam to na później). Kolejność wykonywania jest o tyle istotna, że mimo, że interceptory są bezstanowe, to mają one dostęp do zasobów środowiska serwera, do których dostęp ma komponent EJB, więc i drzewa JNDI, w którym możnaby umieszczać informacje (ot, taki niezalecany efekt poboczny - ang. side-effect - wykonania bezstanowego interceptora). Poza tym istnieje również InvocationContext, o którym za moment, a który może służyć do komunikacji między interceptorami.

Klasa interceptora musi posiadać publiczny bezargumentowy konstruktor (dla przypomnienia: kompilator dostarcza go, jeśli nie istnieje żaden inny konstruktor w klasie, więc sprawa bardzo się upraszcza, jesli tylko sami nie będziemy chcieli jej skomplikować ;-)).

Następnie sekcja 3.4 przechodzi do omówienia zasad związanych z interceptorami (jakby tych już wspomnianych było mało). Mowa jest o kontekstach transakcyjnym i bezpieczeństwa, które są identyczne z tymi aktualnie związanymi z wywoływaną metodą biznesową, dla której one są wywoływane, wyjątkach identycznych z tymi zadeklarowanymi w metodach biznesowych, możliwością wywołania innych usług dostępnych dla komponentu EJB, wsparciu dla wstrzeliwania zależności i ostatecznie kończy się na ograniczeniach interceptorów, które są identyczne z ograniczeniami komponentów EJB. Innymi słowy można traktować interceptory jako metody biznesowe komponentu EJB, dla którego zostały zdefiniowane, z tym, że wywoływane są wcześniej, dodatkowo do wywołania metody biznesowej komponentu.

Dalej rozdział przechodzi do prezentacji 2 rodzajów interceptorów:
  • interceptor zdarzenia zwrotnego związanego z etapem życia komponentu (w skrócie interceptor zwrotny) (ang. lifecycle callback interceptor)
  • interceptor metody biznesowej (ang. business method interceptor)
Pierwszy z nich - interceptor zwrotny - jest definiowany przez annotacje @PostConstruct, @PreDestroy, @PostActivate, @PrePassivate bądź deskryptor instalacji i może być metodą klasy komponentu EJB bądź klasą interceptora. Różnica polega na sygnaturze metody reprezentującej interceptor i w przypadku klasy komponentu EJB sygnatura wygląda następująco:

void <NazwaMetody>()

, a dla dedykowanej klasy interceptora jest:

void <NazwaMetody>(InvocationContext)

Metody mogą być dowolnej widoczności (public, protected, private, domyślny). Niedopuszczalne jest deklaracja interceptora zwrotnego jako final lub static (przypomniam, że interceptor to metoda instancji w Javie).

Te same annotacje opisujące interceptor zwrotny (dalej zwane zwrotnymi) są stosowane w klasie komponentu EJB i klasie interceptora (za pomocą annotacji @PostConstruct, @PreDestroy, @PostActivate, @PrePassivate). Ta sama metoda może być udekorowana różnymi annotacjami zwrotnymi.

Zabronione jest wielokrotne użycie tej samej annotacji zwrotnej w danej klasie komponentu.

Drugi z interceptorów - interceptor metody biznesowej - jest definiowany przez annotację @AroundInvoke lub element around-invoke w deskryptorze instalacji dla metod biznesowych (należących do interfejsu biznesowego) komponentów sesyjnego i sterowanymi komunikatami. Jedynie pojedyńcza metoda udekorowana @AroundInvoke może istnieć w klasie komponentu lub w danej klasie interceptora. Zabronione jest, aby metoda udekorowana przez @AroundInvoke była metodą biznesową.

Wywołanie metody biznesowej wyzwala (jest przechwycone przez) metody klasy komponentu udekorowane przez AroundInvoke jak i klasy interceptorów. Wywołanie metody @AroundInvoke musi pociągać za sobą wywołanie metody InvocationContext.proceed() albo związana metoda biznesowa nie zostanie wywołana jak i kolejne metody @AroundInvoke.

Metody @AroundInvoke mają następującą sygnaturę:

public Object <NazwaMetody>(InvocationContext) throws Exception

Na zakończenie sekcji 3.4 dotyczącej interceptorów opisano klasę javax.interceptor.InvocationContext, który udostępnia kontekst wykonania dla interceptorów (i który wspomniany był kilkakrotnie wcześniej). Identyczna instancja InvocationContext jest przekazywana do każdego interceptora dla danej metody biznesowej lub zdarzenia związanego z etapem życia komponentu i pozwala na przekazywanie informacji między interceptorami.

Najważniejsza z metod w klasie InvocationContext to

public Object proceed() throws Exception

, która powoduje wywołanie kolejnego interceptora bądź, kiedy wywołana przez ostatni z interceptorów, metody biznesowej.