25 stycznia 2007

Java EE 5: Resources, Naming, and Injection

Zanim podejdę do lektury kolejnej specyfikacji postanowiłem sprawdzić jedną rzecz, która męczyła mnie od dawna - rodzaje komponentów w Java EE 5, które podlegają obsłudze mechanizmu wstrzeliwania zależności (DI). Pamiętam, że podczas przygotowań do, bądź w trakcie prezentacji EJB3 na spotkaniu Warszawa JUG, zauważyłem, że strony JSP nie korzystają z DI (mimo, że JSP to ostatecznie servlet). Właśnie teraz, po zakończeniu EJB3 Simplified, a przed rozpoczęciem kolejnej części stwierdziłem, że jest to najlepszy moment, w którym powinienem rozwiać wszelkie wątpliwości z tym związane. I...zabrałem się za lekturę rozdziału 5 specyfikacji Java Platform, Enterprise Edition (Java EE) Specification, v5 (co za zbieg okoliczności z tymi 5-tkami) - Chapter 5: Resources, Naming, and Injection.

Rozdział 5 zawiera dokładnie to, czego potrzebowałem, aby w pełni odpowiedzieć na pytanie: Kogo dotyka dobrodziejstwo DI? Z EJB3 Simplified wiem, że dotyczy to każdego typu komponentu EJB, ale co poza tym? Odpowiedzi udziela rozdział 5 specyfikacji Java EE 5. W nim opisano sposób deklarowania i rozwiązywania zależności od zasobów środowiska i parametrów konfiguracyjnych. Jest to zlepek informacji pochodzących z różnych specyfikacji pomocniczych:
  • Java Metadata (JSR-175 aka adnotacje), która jest częścią Java SE 5 (patrz pakiet: java.lang.annotation)
  • Java Naming and Directory Interface (JNDI) - podobnie jak adnotacje, jest częścią Java SE od wersji 1.3 (patrz pakiet: javax.naming)
  • Common Annotations 1.0 (JSR-250) opisująca adnotacje wspólne dla technologii Java (nie tylko Java EE)
  • Enterprise JavaBeans 3.0 (JSR-220), w której skład wchodzi również specyfikacja Java Persistence API
Na uwagę zasługuje powód zmian i wprowadzenia tylu usprawnień pod postacią adnotacji i połączenia ich z deskryptorem instalacji w specyfikacji Java EE 5. Mamy dwa główne powody wprowadzenia rozwiązań będących przedmiotem rozważań grup standaryzujących skupionych wokół w/w specyfikacji. Po pierwsze, zakłada się, że osoba konstruująca aplikację (ang. application assembler) oraz instalator (ang. application deployer) mają możliwość konfiguracji aplikacji bez konieczności posiadania kodu źródłowego (mimo ich zanużenia - za pomocą adnotacji - w kodzie źródłowym). W Java EE 5 elementy konfiguracyjne jak wartości parametrów, połączenia do zasobów zewnętrznych, odwzorowanie logicznych nazw użytkowników itp. są możliwe w dekryptorze instalacji (w zasadzie wiele się tutaj nie zmieniło w porównaniu z poprzednią wersją specyfikacji Java EE 1.4). Drugi powód to zniesienie obowiązku poznawania szczegółów mechanizmu zarządzania zasobami (będącymi zależnościami) w środowisku uruchomieniowym. Uproszczeniu uległa konfiguracja nazw logicznych zasobów zależnych na ich faktyczne zasoby w środowisku, co nie jest obarczone koniecznością poznawania ich fizycznych nazw i organizacji w środowisku. Zdecydowanie na plus!

W przeciwieństwie do EJB3 Simplified, rozdziały specyfikacji JavaEE są obszerne i zazwyczaj składają się z wielu podrozdziałów. Rozdział 5 - Resources, Naming, and Injection składa się z 54 stron i 13 sekcji, co z pewnością wymaga trochę czasu, aby to przerobić i zrozumieć. Mnie zabrało aż 2 dni i próba zreferowania tematu zawsze kończyła się wycięciem kilku informacji, które z pragmatycznego punktu widzenia nie są istotne (a jeśli są, to i tak przychodzi wtedy pora na zapoznanie się ze specyfikacją bądź dokumentacją produktu - raczej w odwrotnej kolejności).

Dużym udogodnieniem jest uwspólnienie wymagań konfiguracyjnych dla różnych typów komponentów, czyli poznanie adnotacji dla komponentów jednego typu (np. EJB) jest równoznaczne z poznaniem adnotacji dla pozostałych (np. servletów) oraz samego sposób ich użycia.

Wstrzelenie zasobów zależnych sprowadza się do odszukania ich w kontekście nazw JNDI (ang. JNDI naming context), tj. użycie adnotacji jest w tym przypadku jedynie skrótem programistycznym i jest równoznaczne z odszukaniem zasobu w JNDI.

Metody biznesowe komponentu korzystają z elementów/wpisów w środowisku (poza małymi wyjątkami jak metody przechwytujące czy zwrotne). Mogą je pozyskiwać poprzez operacje JNDI bądź skorzystać z metod wyszukujących na obiekcie kontekstu specyficznego dla komponentu (które de facto opakowują bezpośrednie korzystanie z metod JNDI - są ich uproszczeniami, które znoszą obowiązek poznawania szczegółów konfiguracyjnych i metod JNDI). Poza tymi sposobami (które uważam za stosunkowo niskopoziomowe) istnieje mechanizm wstrzeliwania zależności, które umożliwia przekazanie (wstrzelenie) zależności do pola instancji bądź metod modyfikujących (ang. setter) - zauważyłem użycie określenia pole instancji oraz właściwość instancji, gdzie to drugie jest polem instancji z metodą modyfikującą i/lub odczytującą. Dostawca komponentu deklaruje w deskryptorze instalacji lub za pomocą adnotacji wszystkie zasoby środowiska, od których zależy działanie aplikacji. Kontener zarządza kontekstem nazw JNDI i umożliwia ich administrację. Osoba instalująca aplikację ma możliwość inicjowania wartości elementów środowiska, które wskazują na wymagane przez aplikację zasoby środowiska.

Współdzielenie zasobów środowiska

Komponenty deklarują zależności, które dostarczone są przez kontener (serwer aplikacyjny) w przestrzeni nazw JNDI komponentu. Instancje wybranego komponentu w ramach pojedyńczego kontenera współdzielą te same składowe środowiska. Nie ma możliwości modyfikowania środowiska podczas działania aplikacji - innymi słowy przestrzeń nazw jest wyłącznie do odczytu.

Każdorazowe wyszukanie pozycji w przestrzeni nazw java: zwraca nową instancję danego typu z kilkoma drobnymi wyjątkami, gdzie kontener wie, że się nie zmienią (stan obiektu jest niezmienny, singletony, wybrane obiekty współdzielone). Tym samym każdorazowe wstrzelenie zależności poprzez adnotacje jako równoważne wyszukaniu w kontekście komponentu nowej instancji, bądź współdzielonej zgodnie z zasadami wymienionymi powyżej (uwaga na jednostki utrwalania - ang. persistence unit - przy wartości tworzenia schematu drop-create).

Kilka istotnych uwag odnośnie miejsca użycia adnotacji:
  • Pole lub metoda instancji może być dowolnej widoczności (public, private, protected czy domyślnej)
  • Poza klientami aplikacyjnymi (ang. application client) pole i metoda nie mogą być static
  • W przypadku klientów aplikacyjnych pole i metoda musza być static
  • Udekorowane pole nie może być final
  • Domyślna nazwa zasobu wyszukiwanego w kontekście podczas wstrzeliwania zależności to sklejenie nazwy pola wraz z pełną nazwą klasy, np. java:comp/env/pl.jaceklaskowski.exams.ExamSchedulerBean/examScheduler, gdzie java:comp/env wyznacza prywatną cześć przestrzeni nazw dla komponentu podlegającemu DI, pl.jaceklaskowski.exams.ExamSchedulerBean jest nazwą klasy, w której następuje DI (w tym przypadku jest to komponent EJB) oraz examScheduler jest nazwą pola instancji
  • Adnotacja umożliwia również bezpośrednie podanie nazwy zasobu (atrybut name), pod którą można go odszukać w kontekście, która jest względna do przestrzeni java:comp/env. Podobny mechanizm działa dla właściwości (np. udekorowanie setExamScheduler jest równoznaczne z udekorowaniem pola examScheduler).
Dowolny zasób może być wstrzelony do pola lub metody w danej klasie. Nie można zażądać wstrzelenia zasobu o danej nazwie jednocześnie do pola i metody, które wskazują na tę samą zmienną instancji. Dozwolone jest jednak podanie różnych nazw dla metody i pola. Podając inną niż domyślną nazwę pojedyńczy zasób może zostać wstrzelony do wielu pól i metod wielu klas.

Typy komponentów mogących korzystać z DI (to był właśnie powód lektury rozdziału 5!):
  • servlety, filtry, metody obsługujące zdarzenia (ang. event handlers)
  • JSP - klasy znaczników, metody obsługujące zdarzenia związane z bibliotekami znaczników (ang. tag library event listeners)
  • JSF - komponenty zarządzane o zasięgu innym niż none (ang. scoped managed beans)
  • JAX-WS - końcówki serwisów (ang. service endpoints), klasy komponentów
  • EJB - komponenty i metody przechwytujące (ang. interceptors)
  • Klient aplikacyjny (ang. application client) - metoda main oraz metoda zwrotna uwierzytelniająca JAAS
Wszystkie wspierają adnotacje @PostConstruct oraz @PreDestroy, za wyjątkiem klienta aplikacyjnego dla metody main. Wstrzeliwanie zależności odbywa się po stworzeniu instancji typu (poziom JVM), a przed wywołaniem metody biznesowej (poziom serwera aplikacyjnego). Jeśli istnieje @PostConstruct to jest ona wywoływana po DI. Brak zasobu, dla którego istnieje deklaracja zależności (adnotacja) powoduje niedostępność usługi/komponentu.

Adnotacje mogą być związane z samą klasą. W takim przypadku należy podać nazwę zależnego zasobu. Nie powoduje to faktycznego wstrzelenia zależności, a jedynie deklarację pozycji w kontekście komponentu. Niestety, nie odkryłem jeszcze sytuacji, w której chciałbym skorzystać z tej możliwości - wydaje mi się, że jest to po prostu ukłon w stronę administratora, że korzystamy z takiego zasobu wprost, poprzez operacje JNDI, aby wiedział, co należy dostarczyć w środowisku, tj. serwerze aplikacyjnym, aby komponent działał poprawnie (a może jest to układ do samego siebie, w końcu skoro przekazujemy komponent do wykorzystania, to chcemy, aby błedy typu brak zasobów nie wpływał na stan emocjonalny klienta ;-))

Udekorowanie klas może odbywać się na dowolnym poziomie hierarchi klas komponentu. Nie ma znaczenia, że klasa, z której dziedziczy komponent nie jest komponentem podlegającym DI. DI podlega zasadom widzialności pól i metod w Javie, co oznacza, że wszystko, co widoczne w klasie podlegającej DI może zostać udekorowane (mimo, że fizycznie nie występuje w samej klasie mogącej zażądać DI). Klasa pochodna ma prawo nadpisać adnotacje, tj. zmienić typ wstrzeliwanego zasobu, czy nawet zadeklarować brak DI.

Deklaracja składowej środowiska (zasób bądź zmienna) odbywa się albo za pomocą adnotacji, albo wpisów do deskryptora instalacji. Istotna zmiana w Java EE 5 polega na możliwości zaniechania deskryptora na rzecz domyślnych wartości zależności bądź ich deklaracji i konfiguracji za pomocą adnotacji (ale to juz było wiadomo z EJB3 Simplified). Ta sama składowa może być zadeklarowana poprzez adnotację i deskryptora instalacji (w kolejności ich ważności, czyli wartość z deskryptora jest ważniejsza). Jest to sposób na nadpisanie wartości przez osobę składającą aplikację z komponentów czy administratora (bez konieczności posiadania kodu źródłowego, gdzie zadeklarowano, lub nie, zależności poprzez adnotacje). Nie powinno się korzystać z DI w deskryptorze instalacji do pól lub metod, które nie zostały do tego przeznaczone (czyli możliwa jest, ale nie zalecana, sytuacja, gdzie administrator zmodyfikuje deskryptor instalacji - to mu wolno - i wstrzeli zasób do pola/metody, która nie została przez programistę udekorowana - Ale jazda! Szanujmy administratorów! ;-)).

Reguły rozwiązywania wartości elementu środowiska zadeklarowanego za pomocą adnotacji i nadpisanego przez deskryptor instalacji:
  1. Wartość odszukiwana jest w JNDI na podstawie nazwy domyślnej lub podanej explicite
  2. Typ określony w deskryptorze instalacji musi odpowiadać typowi pola bądź właściwości (tj. parametrowi wejściowemu metody modyfikującej)
  3. Opis w deskryptorze nadpisuje description z adnotacji
  4. Miejsce wstrzelenia (jeśli podane) musi wskazywać na udekorowane pole lub metodę modyfikującą
  5. res-sharing-scope (jeśli podane) nadpisuje shareable z adnotacji (nie zaleca się nadpisywania jej, gdyż może zaburzyć działanie komponentu)
  6. res-auth (jeśli podane) nadpisuje authenticationType z adnotacji (nie zaleca się nadpisywania jej, gdyż może zaburzyć działanie komponentu)
Nadpisywanie @EJB oraz @WebServiceRef jest opisywane w specyfikacji EJB i Web Services (dojdę i do nich niebawem...)

Specyfikacja zawiera obowiązki poszczególnych ról występujących podczas tworzenia aplikacji - dostawcy komponentu, osoby składającej aplikację, administratora i dostawcy serwera aplikacyjnego. W ten sposób opisano wszystkie rodzaje pozycji kontekstu i ich konfigurację względem ról.

Proste pozycje środowiska (ang. simple environment entries)

Odpowiadają pozycjom, których wartość jest typem opakowującym typ prosty w Javie wraz z java.lang.String (Character, Byte, etc.)

Pole lub metoda może być udekorowana przez @Resource. Kontener "rozpakuje" typ opakowujący na jego odpowiednik prosty (ang. Java unboxing). authenticationType i shareable nie mogą być podane (nie mają zastosowania).

// konfiguracja wartości pola dostępna administratorowi
@Resource int iloscPoprawnychProb;
Zawsze istnieje możliwość wyszukania pozycji środowiska w java:comp/env.

Dostawca komponentu deklaruje wpisy środowiska za pomocą adnotacji bądź deskryptora instalacji (env-entry). Zasięg widoczności wpisu jest wyłącznie do komponentu, który go zawiera. Ta sama nazwa wpisu może pojawić się wielokrotnie dla różnych komponentów.

Poza deklaracją elementu środowiska poprzez adnotację (w ten sposób deklarujemy, które pole/właściwość podlega DI) mamy możliwość zadeklarować wstrzelenie wartości elementu środowiska bez konieczności korzystania z adnotacji za pomocą znacznika injection-target (w obszarze znacznika env-entry) - uwaga na programowanie w XML znane i (nie)lubiane z Jelly - administratorzy mają kolejną zabawkę, aby instalacja aplikacji trwała latami.

Istnieje możliwość deklarowania wartości domyślnej w kodzie komponentu.

Referencje do komponentów EJB

Administrator wiąże zależności komponentów od komponentu EJB z jego interfejsem domowym lub instancją. Wiązania następują poprzez logiczne nazwy - referencje EJB.

Istnieje również możliwość wiązania referencji EJB z jednego komponentu do EJB w pliku ejb-jar w deskryptorze instalacji bądź poprzez adnotację.

Adnotacja @EJB przypisywana jest do pola albo metody i jedynie dla dowiązania do komponentu sesyjnego (niby wiadome, ale i tak było to dla mnie duużym zaskoczeniem).

Referencja może być do lokalnego/zdalnego interfejsu domowego (EJB 2.1 i poprzednie) albo do interfejsu biznesowego (EJB 3.0). Jeśli korzysta się z referencji do interfejsu biznesowego (EJB3) wstrzelona zostanie już instancja EJB.

Istnieje możliwość precyzyjnego podania wartości adnotacji (z częściowym wykorzystaniem wartości domyślnych - patrz: konfiguracja przez nadpisywanie).

Mimo, że referencje do EJB są elementami środowiska to env-entry nie ma zastosowania. Korzysta się z adnotacji (kod źródłowy) albo znaczników ejb-ref lub ejb-local-ref (deskryptor instalacji).

Opcjonalny znacznik ejb-link precyzuje położenie zależnego komponentu EJB w aplikacji przemysłowej (ear). Można wykorzystać go do przypisywania nazwy logicznej komponentom o tych samych nazwach.

Referencje do fabryki połączeń z zarządcami zasobów (ang. resource manager connection factory)

Faryka połączeń z zarządcami zasobów jest obiektem tworzącym połączenia do zarządcy zasobów (np. javax.sql.DataSource jest fabryką dla java.sql.Connection).

Pole lub metoda może być udekorowana @Resource. Elementy authenticationType i shareable służą do uszczegółowienia konfiguracji dostępu do zasobów.

@Resource javax.sql.DataSource employeeAppDB;
Specyfikacja zaleca, aby fabryki połączeń danego typu były związane z podgałęziami java:comp/env, tj.
  • java:comp/env/jdbc dla fabryk połączeń JDBC (javax.sql.DataSource)
  • java:comp/env/jms dla fabryk połączeń JMS z systemami przesyłania komunikatów (javax.jms.QueueConnectionFactory, javax.jms.TopicConnectionFactory, javax.jms.ConnectionFactory)
  • java:comp/env/mail dla połączeń JavaMail (javax.mail.Session)
  • java:comp/env/url dla połączeń poprzez URLConnection (java.net.URL)
Deklaracja elementów środowiska dla fabryk odbywa się w deskryptorze instalacji za pomocą resource-ref.

Referencje kanałów komunikacji (ang. message destination references)

Tutaj nie ma wiele zmian porównując z referencjami fabryk połączeń. Korzystamy z @Resource bez authenticationType oraz shareable (nie mają zastosowania).

Za pomocą adnotacji @Resource, nie ma możliwości zdefiniowania, czy związany zasób służy do odbioru czy wysyłania komunikatów. Służy do tego deskryptor instalacji z pomocą znacznika message-destination-ref.

@Resource javax.jms.Queue stockQueue;

Referencje UserTransaction

Niektóre komponenty mają możliwość zarządzania tranzakcją (rozpoczęcie, zatwierdzenie i wycofanie). Do dowiązania do obiektu UserTransaction służy java:comp/UserTransaction albo adnotacja @Resource dla typu userTransaction.

Wstrzelenie lub udostępnienie zasobu w kontekście jest kontrolowane przez serwer aplikacyjny i mimo zadeklarowania referencje mogą nie istnieć (ale nie doszedłem, które są takimi wrażliwymi typami komponentów).

@Resource UserTransaction tx;

Referencje do TransactionSynchronizationRegistry

Interfejs TransactionSynchronizationRegistry może być wykorzystywany przez komponenty systemowe jak zarządcy utrwalania stanu (ang. persistence managers) dla EJB lub komponentów aplikacji internetowych.

Możliwe dekorowanie @Resource albo wyszukanie java:comp/TransactionSynchronizationRegistry w JNDI.

Wstrzelenie lub udostępnienie zasobu w kontekście jest kontrolowane przez serwer aplikacyjny i mimo zadeklarowania referencje mogą nie istnieć (ale nie doszedłem, które są takimi wrażliwymi typami komponentów).

Referencje do szyn ORB

Wstrzelenie za pomocą @Resource lub odszukanie java:comp/ORB w JNDI.

Serwer aplikacyjny musi dostarczyć obiekt do każdego typu komponentu poza appletami.

@Resource ORB orb;

Referencje do jednostek zapisu (ang. persistence unit references)

Udostępnia logiczną nazwę dla fabryki utrwalania stanu komponentów encyjnych.

Jednostki zapisu definiowane są w persistence.xml zgodnie z zasadami opisanymi w specyfikacji Java Persistence API (część specyfikacji Enterprise JavaBeans 3.0).

Pole lub metoda może zostać udekorowana adnotacją @PersistenceUnit. Opcjonalnie można podać nazwę jednostki w JNDI za pomocą elementu name. Element unitName wskazuje na nazwę jednostki jak zdefiniowano w persistence.xml.

@PersistenceUnit
EntityManagerFactory emf;

@PersistenceUnit(unitName="InventoryManagement")
EntityManagerFactory inventoryEMF;
Specyfikacja zaleca, aby wszystkie jednostki utrwalania były dostępne w poddrzewie java:comp/env/persistence.

Deklaracja zależności w deskryptorze instalacji odbywa się poprzez persistence-unit-ref.

Identyczne nazwy jednostek (wykorzystywanych przez różne komponenty) można przypisać do nazw logicznych w deskryptorze instalacji z wykorzystaniem '#' w znaczniku persistence-unit-name.

<persistence-unit-name>
../lib/inventory.jar#InventoryManagement
</persistence-unit-name>

Referencje do kontekstów utrwalania (ang. persistence context references)

Udostępnia logiczną nazwę dla kontekstu utrwalania stanu komponentów encyjnych.

Jednostki zapisu definiowane są w persistence.xml zgodnie z zasadami opisanymi w specyfikacji Java Persistence API (część specyfikacji Enterprise JavaBeans 3.0).

Pole lub metoda może zostać udekorowana adnotacją @PersistenceContext. Opcjonalnie można podać nazwę jednostki w JNDI za pomocą elementu name. Element unitName wskazuje na nazwę jednostki jak zdefiniowano w persistence.xml. Opcjonalny element type wskazuje na rodzaj kontekstu:
  • tranzakcyjny (ang. transaction-scoped) - domyślny
  • rozszeżony (ang. extended)

@PersistenceContext
EntityManager em;
albo

@Resource SessionContext ctx;
...
EntityManager em = (EntityManager) ctx.lookup("persistence/InventoryAppMgr");
Deklaracja w deskryptorze instalacji odbywa się poprzez persistence-context-ref. Istnieje również możliwość zadeklarowania miejsca wstrzelenia zależności poprzez deskryptor instalacji.