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ć!