30 września 2007

Elementy programowania aspektowego w EJB 3.0 - część 2: Interceptory biznesowe

W poprzedniej części o programowaniu aspektowym w EJB 3.0 - Elementy programowania aspektowego w EJB 3.0 - część 1 - stworzyłem interceptor TracingInterceptor, który przechwytywał wywołania metod biznesowych i wyświetlał informacje z nimi związane.

Tym razem stworzę nowe ziarno EchoBean, które udostępni metodę biznesową echo(String). Jej wywołanie będzie poprzedzone wywołaniem interceptora biznesowego TracingInterceptor zdefiniowanego w poprzedniej notatce o interceptorach oraz całkiem nowego interceptora biznesowego, który będzie niebiznesową metodą ziarna odnotujWykorzystanieUslugi, którego celem będzie gromadzenie informacji o wykorzystaniu usługi dostarczanej przez ziarno. Jako warstwę kliencką do uruchomienia ziarna wykorzystam mechanizm usługi sieciowej (ang. web service).

Najpierw kilka faktów ze specyfikacji EJB 3.0 rozdział 12: Interceptors.

Dowolna liczba klas interceptorów może być zdefiniowanych dla pojedyńczej klasy ziarna.

Interceptor biznesowy może być metodą dedykowanej klasy interceptora bądź metodą niebiznesową ziarna. Związanie klasy interceptora z ziarnem następuje poprzez adnotację @Interceptors natomiast wskazanie metody interceptora poprzez @AroundInvoke (oczywiście istnieją odpowiedniki w postaci znaczników XML w deskryptorze rozmieszczenia, o czym później).

Metody AroundInvoke mogą być zdefiniowane na klasie macierzystej (nadklasie) klasy ziarna lub interceptora. Jednakże, jedynie pojedyńcza metoda AroundInvoke może być zdefiniowana dla danej klasy. Metoda AroundInvoke nie może być metodą biznesową ziarna.

Metody AroundInvoke mogą być dowolnego kwalifikatora dostępu (widoczności) - public, private, protected lub "package protected". Metody AroundInvoke nie mogą być final oraz static.

Metoda AroundInvoke może wywoływać dowolny komponent lub zasób, który może być wywoływany przez ziarno, np skorzystać z zarządcy trwałego, aby odnotować wykonanie metody do bazy danych.

Wykonanie metod AroundInvoke następuje w ramach tego samego kontekstu transakcyjnego i bezpieczeństwa jak metoda biznesowa, z którą jest związana.

Metoda interceptora AroundInvoke może być przypisywana per metoda biznesowa ziarna lub dla wszystkich metod biznesowych klasy ziarna w zależności od umiejscowienia adnotacji @Interceptors (bądź analogicznie wykorzystania znacznika <interceptor-binding> w deskryptorze rozmieszczenia)

Metody AroundInvoke wykonywane są w tym samym stosie wywołań Java jak metoda biznesowa ziarna, z którym są związane.

Sprawdźmy to w działaniu.

W poprzedniej części zaprezentowałem interceptor zdefiniowany w dedykowanej dla niego klasie - TracingInterceptor - tym razem metoda interceptora AroundInvoke będzie częścią ziarna.

Tworzymy ziarno bezstanowe EchoBean, które jest jednocześnie usługą sieciową EchoService.

package pl.jaceklaskowski.ejb3.interceptors;

import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptors;
import javax.interceptor.InvocationContext;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;

@WebService(serviceName = "EchoService")
@Stateless
@Interceptors(value = TracingInterceptor.class)
public class EchoBean implements EchoLocal {

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

@WebMethod(operationName = "echo")
public String echo(@WebParam(name = "message") String message) {
String response = message + "..." + message + "..." + message;
logger.info("echo: " + response);
return response;
}

@AroundInvoke
Object odnotujWykorzystanieUslugi(InvocationContext ctx) throws Exception {
logger.info("@AroundInvoke odnotujWykorzystanieUslugi: Odnotowano wykonanie metody biznesowej " +
ctx.getTarget().getClass().getSimpleName() + "." + ctx.getMethod().getName() + "()");
return ctx.proceed();
}
}

z następującym, bardzo skromnym interfejsem lokalnym EchoLocal:

package pl.jaceklaskowski.ejb3.interceptors;

import javax.ejb.Local;

@Local
public interface EchoLocal {

String echo(String message);

}

Dla przypomnienia zaprezentuję interceptor TracingInterceptor:

package pl.jaceklaskowski.ejb3.interceptors;

import java.util.logging.Logger;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

public class TracingInterceptor {

Logger logger = Logger.getLogger(TracingInterceptor.class.toString());

@AroundInvoke
public Object trace(InvocationContext ctx) throws Exception {
logger.info("TracingInterceptor: Wykonywana metoda biznesowa " +
ctx.getTarget().getClass().getSimpleName() + "." + ctx.getMethod().getName() + "()");
return ctx.proceed();
}
}

Uruchamiamy ziarno na serwerze aplikacyjnym Java EE 5, np. Glassfish v2 bądź Apache Geronimo 2.0.1 i wykonujemy usługę sieciową EchoService. Uruchamiam ziarno z poziomu NetBeans IDE 6.0 - menu Test Web Service.


Automatycznie otwiera się strona wskazująca na usługę sieciową EchoService.


Wpisuję dowolny ciąg znaków, np. jacek (ot tak całkowicie przypadkiem padło na moje imię ;-)) i zatwierdzam wybierając przycisk echo.


W dzienniku zdarzeń serwera (w tym przypadku Glassfish v2) pojawiają się następujące wpisy:

TracingInterceptor: Wykonywana metoda biznesowa EchoBean.echo()
@AroundInvoke odnotujWykorzystanieUslugi: Odnotowano wykonanie metody biznesowej EchoBean.echo()
echo: jacek...jacek...jacek

Interesującą kwestią, do której wrócę, jest kolejność wykonywania interceptorów, a dokładniej mówiąc metod interceptorów.