01 czerwca 2010

Dokończenie rozdziału 2. Koncepcje ze specyfikacji Java EE 6 Contexts and Dependency Injection (CDI)

Potwierdza się zasada, że jeśli tylko regularnie poświęcić chwilę, np. każdego dnia, na rozpoznawanie nowego, to po jakimś czasie nowe stanie się zwyczajne, aby ostatecznie stać się zrozumiałym. Tak też się dzieje w kontekście mojego poznawania specyfikacji JSR-299: Contexts and Dependency Injection for the Java EE platform (w skrócie CDI). Obłożyłem się specyfikacją, artykułami i przeglądam grupy dyskusyjne, i z każdym dniem wszystko staje się jasne (zgoda, może niekonieczne jasne, ale na pewno jaśniejsze).

Do tej pory zrelacjonowałem ze specyfikacji CDI (wszystkie w kategorii cdi):
  1. Nauka Java EE 6 CDI (JSR-299) - relacja z lektury pierwszych dwóch rozdziałów specyfikacji
  2. Lektura specyfikacji CDI (JSR-299) za mną, NetBeans 6.9 RC1 wydane i JSR-330 Dependency Injection for Java
  3. Rozdział 2. Koncepcje ze specyfikacji Java EE 6 Contexts and Dependency Injection (CDI)
  4. i jeden, dwa z przemyśleniami różnymi.
Dzisiaj, od dawna planowane, dokończenie relacji z rozdziału 2. Concepts.

W ostatnim wpisie w temacie CDI - Rozdział 2. Koncepcje ze specyfikacji Java EE 6 Contexts and Dependency Injection (CDI) - padło pytanie, które dla wielu jest banalne, ale dla pozostałych niekoniecznie:

Co różni instancję klasy stworzoną przez new, a tymi, przekazanymi przez kontener?

W Javie do tworzenia nowych obiektów (instancji) służy operator new. Możnaby przyjąć, że to funkcja specjalnego traktowania, gdzie parametrem wejściowym jest typ, którego instancję tworzymy, opcjonalnie z parametrami opisującymi jego stan początkowy. Tutaj żadnej magii nie ma. I nie będzie w CDI, Seam, Spring Framework, Guice, a nawet Aspect-Oriented Programming (AOP), jeśli będziemy pamiętać, że nie ma (znanego mi) sposobu konstruowania obiektów niż właśnie przez new. Tutaj się pojawia moje niezrozumienie dyskusji na temat różnic między CDI a AOP. Oba wymagają środowiska uruchomieniowego, nazywanego powszechnie kontenerem, w którym będą żyły nasze obiekty. Mówi się jednak, że jakaś różnica jest. Podobnie z orędownikami Spring Framework i jego konfiguracji opartej na XML, a CDI, w którym opieramy się głównie na adnotacjach. Aczkolwiek w obu, Spring Framework i CDI, możemy wykorzystywać XML do opisywania zależności i ich miejsc wstrzeliwania, to jednak i tutaj pojawiają się głosy nad wyższością Spring Framework i jego XML (nawet, jeśli nadto obszerny) a CDI z adnotacjami. Tak czy owak, we wszystkich uruchamiany jest kontener, który tworzy środowisko dla grafów obiektów (wyrażających interakcje między nimi), który w pewnym momencie uruchamia obiekt główny - to coś, co możemy nazwać początkiem naszej aplikacji.

A wracając do relacji z lektury specyfikacji CDI...

Sekcja 2.4 Scopes omawia przestrzeń dostępności ziaren (przez to i ich widoczności). Mówimy o ziarnach z określonym zasięgiem (ang. scoped beans), co oznacza, że tak na prawdę, przynależą one do innego obiektu - zasięgu - którego zniszczenie niszczy obiekty w nim żyjące. Każde ziarno jest przypisane do jednego i tylko jednego zasięgu. Jedynie ziarna o danym zasięgu, tj. występujące w danym obszarze, mogą ze sobą "rozmawiać".

Określenie zasięgu dla ziarna następuje przez adnotacje należące do pakietu javax.enterprise.context, np. @SessionScoped, który znamy ze specyfikacji Java Servlets.

Możemy tworzyć własne zasięgi, co sprowadza się do stworzenia nowej adnotacji oznaczonej meta-adnotacjami @javax.inject.Scope lub @javax.enterprise.context.NormalScope.

Określenie zasięgu ziarna to przypisanie do niego adnotacji zasięgu - na poziomie klasy ziarna, metody/pola produkującego.

Dla zwrócenia uwagi: ziarno musi należeć do jednego i tylko jednego zasięgu. Innymi słowy: nie możemy być jednocześnie w domu i w pracy (aczkolwiek takie anomalie coraz częściej zachodzą w naszym życiu).

Domyślny zasięg określony jest przez stereotypy. O tym za moment.

2.5 Nazwa EL

Domyślnie, ziarno nie ma nazwy, jest bezimienne. Nadanie nazwy następuje przez adnotację @javax.inject.Named. Wartość @Named przypisuje nazwę, a bez niego nazwa domyślna to nazwa klasy rozpoczynając od małej litery. Tylko wtedy ziarno jest widoczne dla JSP, JSF czy innych technologii korzystających z Unified EL.

Adnotację @Named przypisujemy do klasy lub metody/pola produkującego.

2.6 Alternatywy

Ziarnem alternatywnym nazywamy ziarno, które jest zdefiniowane w pliku konfiguracyjnym CDI - beans.xml, a które przesłania ziarno, dla którego jest alternatywnym.

Ziarno alternatywne definiujemy przez adnotację @Alternative na poziomie klasy lub metody/pola produkującego. Adnotacja może być przypisana również niewprost przez stereotyp.

2.7 Stereotypy

Stereotyp to adnotacja oznaczona meta-adnotacją @javax.enterprise.inject.Stereotype określająca rolę ziarna w aplikacji. Jest to sposób na nadanie ziarnom pewnych cech wspólnych, które rozumiane są przez aplikację, np. @Action lub @Secure. Jest to nic innego jak meta-adnotacja i jej interpretacja zależy od samej aplikacji.

Przypisanie stereotypu do ziarna jest możliwe przez udekorowanie klasy lub metody/pola produkującego.

Ziarno może mieć zero lub więcej stereotypów.

Stereotyp może mieć przypisany zasięg, który niewprost jest przypisywany ziarnu po zaaplikowaniu stereotypu.

Stereotyp może mieć przypisane interceptory.

Stereotyp może mieć przypisaną pustą (bez wartości dla atrybutu value) adnotację @Named, co spowoduje, że każde ziarno z tym stereotypem będzie miało domyślną nazwę EL.

Stereotyp może być oznaczony przez @Alternative, co automatycznie sprawia, że ziarna z nim są alternatywnymi do innych ziaren (zastępują/przesłaniają je) - przydatne w mockowaniu.

Stereotyp może mieć stereotypy.

Wbudowany stereotyp @javax.enterprise.inject.Model służy do wskazania ziaren będących modelem w aplikacji MVC, np. opartej na JavaServer Faces (JSF).

Tyle. Tym samym zakończyłem relację rozdziału 2. Concepts. Kolejna relacja po długim weekendzie, w poniedziałek. Wypoczywajcie bez komputerów, ale przy specyfikacjach :-)

Gorąco polecam lekturę na nadchodzące dni, która bardzo delikatnie wprowadza w temat CDI:
p.s. Z ostatniej chwili - Ruszyła rejestracja na Javarsovia 2010! Jakkolwiek nie będzie podczas konferencji Javarsovia 2010 dedykowanej prezentacji nt. CDI, ale i tak poruszane tematy gwarantują dobrą zabawę. Rejestrujcie się! Konferencja jest całkowicie bezpłatna.

5 komentarzy:

  1. Bardzo zaciekawiło mnie pojawienie się JSR-299 i dosyć wnikliwie swego czasu studiowałem tę specyfikację. Natrafiłem jednak na rzecz, która bardzo mnie dziwi i - wydaje mi się - uniemożliwia korzystanie z dependency injection w tym wydaniu.

    Chodzi mianowicie o konfigurację wstrzykiwania obiektów. W dotychczas używanych przeze mnie kontenerach IoC (głównie Spring, ale bawiłem się również Spring .NET, Castle Windsor, Structure Map, Autofac i Uniti Application Block) można zdefiniować który obiekt jest gdzie wstrzykiwany. Tymczasem w JSR-299 w ogóle czegoś takiego nie widzę. O ile dobrze rozumiem tę specyfikację, wstrzykiwanie jest oparte wyłącznie o adnotacje i nie ma możliwości utworzenia dwóch wstrzykiwanych obiektów o takiej samej charakterystyce, a następnie zdecydowaniu w pliku konfiguracyjnym (czy gdziekolwiek indziej) który z nich ma zostać ostatecznie użyty.

    Czy ktoś może wyprowadzić mnie z błędu?

    OdpowiedzUsuń
  2. Da się to rozwiązać. W najprostszym podejściu można skorzystać z adnotacji @New. W dalszym, zakładając że jeśli chcesz mieć dwie instancje to CZYMŚ muszą się one różnić, wtedy opisać to można stereotypem. Są też inne możliwości.
    Ważniejsze pytanie jest takie, dlaczego potrzebujesz 2 instancje obiektu mające taką samą charakterystykę? Rozumiem, że mają one też inny 'scope'? Jakiś przykład? :)

    OdpowiedzUsuń
  3. Przykład pierwszy dotyczy testów automatycznych. Załóżmy, że mam dwie klasy: klient i jakiś serwis, serwis jest wstrzykiwany do klienta. Podczas tworzenia testów z jakiegoś powodu chcę napisać własny stub serwisu i wstrzyknąć go do klienta.

    W Springu mam dwa pliki konfiguracyjne i podczas testów korzystam z pierwszego, podczas uruchamiania aplikacji z drugiego. Kod nie posiada absolutnie żadnych adnotacji.

    W CDI (JSR-299) musiałbym serwis i klienta oznaczyć odpowiednimi adnotacjami (co samo w sobie już uważam za design smell). Co więcej, musiałbym faktyczną implementację serwisu oznaczyć inaczej, niż stub. - jeszcze więcej adnotacji.

    Wreszcie dochodzę do kwestii uruchamiania aplikacji i uruchamiania testów - nie wiem jak zrobić, aby w zależności od kontekstu inna klasa (serwis lub stub serwisu) była wstrzykiwana do klienta, skoro nie mam plików konfiguracyjnych.


    Przykład drugi dotyczy łańcucha przetwarzania. Powiedzmy, że mam strumień bajtów, który przepuszczam przez kilka komponentów przetwarzających. Rozwiązanie ma być konfigurowalne, chcę dowolnie włączać i wyłączać poszczególne filtry. Napisałem ich kilka i wypuszczam aplikację, w pliku konfiguracyjnym mogę podać które filtry wstrzykiwać do klasy przetwarzającej. Każdy z tych filtrów realizuje oczywiście inną funkcję, ale z punktu widzenia aplikacji mają dokładnie tę samą charakterystykę. Nie chcę niczego na sztywno ustalać w kodzie (adnotacjami), chcę mieć po prostu plik konfiguracyjny.


    To przykłady bez kodu źródłowego. Jeśli są nie dość klarowne, mogę przygotować przykłady przygotowane pod Springa i spróbujemy je przełożyć na CDI.

    OdpowiedzUsuń
  4. Od tego jest np @Alternative. Masz implementacje servisu do produkcji, a do testow masz testowa - dajesz jej adnotacje @Alternative i uruchamaisz ja w beans.xml i juz. Do tego np my uzywamy mavena, wiec standardoy serwis jest w src/main/java a alternatywa jest w /src/test/java, wiec w wynikowym jarze nie bedzie zawarta.

    OdpowiedzUsuń
  5. W kwestii pierwszego problemu przykład jak ja do tego bym podszedł zamieściłem tutaj: http://damianlukasik.blogspot.com/2010/06/testowanie-jsr-299-z-junit.html
    Do filtrowania a'la CDI skorzystałem z dekoratorów.
    http://docs.jboss.org/weld/reference/1.0.1-Final/en-US/html/decorators.html
    Niedługo postaram się opisać to rozwiązanie.

    OdpowiedzUsuń