14 stycznia 2007

@PostConstruct oraz @PreDestroy znane z EJB3 są również w JSF 1.2!

Przeglądając fora dotyczące tematyki JavaServer Faces (JSF) natrafiłem na pytanie dotyczące działania annotacji @PostConstruct oraz @PreDestroy w...JSF. Niedawno podziwiałem ich wprowadzenie do EJB3, a tu nagle informacja o ich istnieniu w JSF 1.2. Szybka lektura specyfikacji JSF 1.2 rozdziału 5.4 Leveraging Java EE 5 Annotations in Managed Beans i wszystko jasne. Wiele z annotacji, o których istnieniu dowiedziałem się ze specyfikacji EJB3 Simplified, istnieje również w JSF 1.2. Teraz napiszę nawet, że to i logiczne, ale jeszcze dzisiejszego ranka nie byłbym tego taki pewny.

Na moment zapomniałem kompletnie o EJB3 i przeczytałem rozdział 5.4 w specyfikacji JSF 1.2. Co nam dostarczają serwery aplikacyjne Java EE 5? Spójrzmy na to szerzej niż tylko na dwie wspomniane annotacje.

Każdy komponent zarządzany (ang. managed bean) o zasięgu request, session oraz application ma możliwość korzystania z wstrzeliwania zależności (DI). DI wykonywane jest przed udostępnieniem komponentu w aplikacji. Poprawne annotacje, z których można korzystać, to te opisane w specyfikacji Servlet 2.5, tj.:
  • @Resource
  • @Resources
  • @EJB
  • @EJBs
  • @WebServiceRef
  • @WebServiceRefs
  • @PersistenceContext
  • @PersistenceContexts
  • @PersistenceUnit
  • @PersistenceUnits
(każda annotacja dla pojedyńczej zależności ma swój odpowiednik dla wielu odwołań).

Podobnie jak to ma miejsce w EJB3, annotacjami można dekorować zmienne instancji lub metody modyfikujące zmienne (ang. setters).

Pierwsze wywołanie metody komponentu zarządzanego w aplikacji spowoduje wykonanie DI (instancje komponentów są tworzone z opóźnieniem - ang. lazy instantiation).

Podrozdział 5.4.1 Managed Bean Lifecycle Annotations omawia wspomniane annotacje @PostConstruct oraz @PreDestroy. Jedynie metody komponentów o widoczności (zasięgu) request, session oraz application udekorowane annotacjami będą wywoływane.

Metoda @PostConstruct będzie wywoływana po stworzeniu instancji komponentu, po DI, ale przed udostępnieniem komponentu w danym zasięgu (czyli przed wykonaniem jego metod).

Metoda @PreDestroy wywoływana jest przed usunięciem instancji komponentu zarządzanego.

Wyłącznie metody spełniające poniższe kryteria mogą być udekorowane @PostConstruct oraz @PreDestroy:
  • metoda musi być bezargumentowa
  • zwracany typ z metody musi być void
  • nie możliwa jest deklaracja kontrolowanych wyjątków (ang. checked exception)
  • metoda może być dowolnej widoczności - public, protected, private lub default (aka package private)
Jeśli metoda zakończy się niekontrolowanym wyjątkiem (ang. unchecked exception) to:
  • @PostConstruct spowoduje brak dostępności komponentu dla aplikacji, więc metody na nim nie będą mogły być wywołane
  • @PreDestroy ignoruje ich pojawienie się (serwer aplikacyjny może odnotować wyjątek)
Mały przykładzik na zastosowanie @PostConstruct oraz @PreDestroy na zakończenie relacji nie zawadzi (kolejność metod ułożyłem, aby odpowiadała ich wywołaniu dla zwiększenia czytelności):

package pl.jaceklaskowski.jsf12;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class Quiz {

public Quiz() {
System.out.println("Konstruktor Quiz.<init>() wykonany");
}

@PostConstruct
protected void init() {
System.out.println("init() wykonane");
}

public String businessMethod() {
System.out.println("businessMethod() wykonane");
return "success";
}

@PreDestroy
protected void destroy() {
System.out.println("destroy() wykonane");
}

}

Wystarczy zadeklarować komponent Quiz w faces-config.xml:

<managed-bean>
<managed-bean-name>quiz</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.jsf12.Quiz</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>

i uruchomienie aplikacji z komponentem uwidacznia się w GlassFish v2 b31 tak:

Konstruktor Quiz.<init>() wykonany
init() wykonane
businessMethod() wykonane
destroy() wykonane

Świadomie użyłem nazw metod - init() oraz destroy(). Zbyt często pojawiają się one w moich komponentach, aby ich zbieżność nie sugerowała możliwego zastosowania. Najwyższa pora na projekt z Java EE 5 w tle ;-)

Dodatkowo doszukałem się informacji o @PostConstruct i @PreDestroy w dokumentacji JBoss Seam - Chapter 5. Events, interceptors and exception handling. Na szczególną uwagę zasługuje sekcja 5.2. Seam interceptors, a właściwie jej zakończenie, gdzie napisano:

You can even use Seam interceptors with JavaBean components, not just EJB3 beans!

EJB defines interception not only for business methods (using @AroundInvoke), but also for the lifecycle methods @PostConstruct, @PreDestroy, @PrePassivate and @PostActive. Seam supports all these lifecycle methods on both component and interceptor not only for EJB3 beans, but also for JavaBean components (except @PreDestroy which is not meaningful for JavaBean components).

Zastanawiam się jaki to ma związek z wymaganiem odnośnie komponentów zarządzanych w JSF 1.2 (na których JBoss Seam opiera swoje działanie)? Czyta się to tak, jakby korzystanie z annotacji @PostConstruct oraz @PreDestroy to była wyłącznie cecha Seama, a co się właśnie dowiedziałem i JSF 1.2 to ma. Oczywiście udostępnienie annotacji dla dowolnych komponentów JavaBeans (POJO), to więcej niż dla komponentów EJB3, czy JSF1.2, ale nie potrafię wyobrazić sobie sytuacji, w której chciałbym korzystać z annotacji poza "uaktywnieniem" komponentów jako będące komponentami EJB3, albo JSF1.2. W przypadku EJB3 to jedynie dodanie kolejnej annotacji (@Stateless, @Stateful, @MessageDriven). Dla JSF 1.2 sprawa się lekko komplikuje, bo wymagana jest deklaracja komponentu w pliku konfiguracyjnym faces-config.xml (chyba, że i tym razem pojawiło się kolejne udoskonalenie w specyfikacji, o którym nie wiem). Tak, czy owak, czy to tak wiele? Czy rzeczywiście JBoss Seam udostępnia coś nadzwyczajnego?! Chyba na wieczór podwyższył mi się poziom krytyki dla JBoss Seam. Pora iść spać!