15 lipca 2010

Google Guice i JSR-330 Dependency Injection for Java

W Recenzja "Dependency Injection, Design patterns using Spring and Guice" z Manning pisałem o moim zachwycie Google Guice. Okazało się, że jest to jedno z tych rozwiązań, którego istnienia nie doceniałem, a gdyby zapytać mnie o cechy wyróżniające go od innych, podobnych jemu, z pewnością nie potrafiłbym wskazać tych, które mogłyby sprawić, że stanie się właściwym rozwiązaniem w projekcie. Obecna moja znajomość Guice sprowadza się do umiejętności wymówienia jego nazwy i opisania jako kontener DI (ang. dependency injection). Niewiele.

Po tej książce, potrzebowałem bliższego, bardziej praktycznego spotkania z Guice. Kiedykolwiek myślę o kontenerze DI, na myśl przychodzi Spring Framework (czasami, ale baaaardzo rzadko, takie egzotyczne rozwiązania jako HiveMind, PicoContainer, może nawet JBoss Seam, Apache XBean czy dawno zapomniany Avalon). Moje ostatnie dokonania na gruncie Spring Framework w postaci warsztatu i po nim następujących artykułów (patrz Tworzenie samodzielnej aplikacji ze Spring Framework i Hibernate w NetBeans IDE 6.9) trochę mnie zmęczyły i kiedykolwiek zabierałem się za kolejną konfigurację w XMLu, wspominałem stare, dobre czasy z serwerem aplikacyjnym Java EE. Ktoś mógłby wspomnieć o możliwości konfiguracji Springa przez adnotacje, ale jakby tego nie ująć, każdorazowe spotkanie ze Springem zawsze u mnie kończy się jednak na XMLu. Brrr...

Kiedy zabrałem się za poznawanie specyfikacji Java EE 6, zacząłem od JSR 299: Contexts and Dependency Injection for the Java EE platform (w skrócie CDI). Tam naczytałem się o wielu innych specyfikacjach i czego mi brakowało, to jego użycia poza serwerem aplikacyjnym JEE6. W końcu jakoś się udało i nawet spisałem moje doświadczenia w artykule Contexts and Dependency Injection (CDI) praktycznie - zestawienie środowiska z JBoss Weld, Arquillian i Apache Maven 2. Należy jednak zwrócić uwagę na słowo "jakoś", bo ono oddaje klimat tego artykułu - CDI w obecnym kształcie jest możliwe do uruchomienia poza serwerem aplikacyjnym, ale jego główne użycie jest właśnie w ramach serwera (chociażby JSF2). Nie pasowało mi tu stworzenie kolejnej specyfikacji JSR-330 Dependency Injection for Java. Możnaby powiedzieć, że DI to CDI, ale coś je jednak musi różnić, skoro obie istnieją pod parasolem JEE6. I różnią się - zespołem pracującym nad nimi :-) Przy CDI pracował Gavin King (JBoss Seam, Hibernate), a przy DI pracowali Bob Lee (Guice) i Rod Johnson (współtwórca Spring Framework). To może tłumaczyć pojawienie się obu, zamiast stworzenia jednej właściw(sz)ej.

Ostatnio zasugerowano mi, aby sprawdzić wsparcie CDI przez Springa i przez myśl przeszło mi, aby się tym zająć, ale zamiast CDI skończyło się na JSR330 DI. Zacząłem od dokumentacji pakietu javax.inject. To właśnie wtedy stwierdziłem, aby zamiast Springa, zbadać Guice.

Po javadoc wzięło mnie na lekturę Dependency injection with Guice, który okazał się bardzo przystępny  merytorycznie, wciąż jednak stosunkowo krótki, aby się nie znużyć jedynie czytaniem. Później przyszła pora na Google Guice :: Getting Started i Google Guice :: Spring Comparison. Z nimi i wcześniejszą lekturą książki Dependency Injection - Design patterns using Spring and Guice byłem gotów do wstępnych testów, tym razem z IntelliJ IDEA.

Zacznijmy od końca - od uruchomienia Guice i pobrania kompletnego obiektu z już wstrzelonymi zależnościami.
package pl.jaceklaskowski.javaee.di;

import com.google.inject.Guice;
import com.google.inject.Injector;

public class Main {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new DictionaryModule());
        EnglishPolishDictionaryService dictionaryService = injector.getInstance(EnglishPolishDictionaryService.class);
        System.out.println(dictionaryService.translate("turn out"));
    }
}
Konfigurację Guice realizuje się przez moduły - klasy javowe, w których przypisujemy interfejsy do ich implementacji, typów, które będą przedmiotem wstrzeliwania zależności.
package pl.jaceklaskowski.javaee.di;

import com.google.inject.AbstractModule;

public class DictionaryModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(VocabularyProvider.class).to(EnglishVocabularyProvider.class);
    }
}
Każdy moduł rozszerza klasę com.google.inject.AbstractModule. To w module konfigurujemy Guice na potrzeby naszej aplikacji, gdzie następuje przypisanie co będzie przypisane gdzie za pomocą konstrukcji
bind([GDZIE]).to([CO]).in([OBSZAR])
W powyższym przykładzie, jakiekolwiek wystąpienie zmiennej typu VocabularyProvider zostanie zainicjowane zmienną typu EnglishVocabularyProvider, domyślnie jako singleton.

Ostatnim, aczkolwiek najważniejszym elementem aplikacji korzystającej z Guice, jest wykorzystanie adnotacji @com.google.inject.Inject, która oznacza miejsca podlegające mechanizmowi wstrzeliwania zależności (stąd też nazwa dla specyfikacji JSR 330 DI - atInject). Poniższa klasa prezentuje adnotację @Inject w akcji.
package pl.jaceklaskowski.javaee.di;

import com.google.inject.Inject;

import java.util.Locale;
import java.util.Map;

public class EnglishPolishDictionaryService implements DictionaryService {

    private final Map<String, String> vocabulary;

    @Inject
    EnglishPolishDictionaryService(VocabularyProvider vocabularyProvider) {
        this.vocabulary = vocabularyProvider.provideVocabularyFor(Locale.ENGLISH);
    }

    public String translate(String word) {
        return "[PL]" + word; 
    }
}
Każde wystąpienie @Inject jest miejscem, gdzie nastąpi wstrzelenie zależności przez Guice. Czy to konstruktor (którego wszystkie parametry podlegają wstrzeliwaniu przez Guice), czy metoda (o dowolnej nazwie i sygnaturze), czy pole instancji, wszystkie te miejsca są inicjowane przez Guice.

p.s. W ostatniej klasie EnglishPolishDictionaryService popełniłem błąd związany z niepoprawnym użyciem wstrzeliwania zależności (oferowanym przez Guice, ale to nie ma w tym przypadku znaczenia). Sama aplikacja działa, ale właściwe użycie DI powinno odpowiadać zasadzie Inject only direct dependencies. Przykład obrazuje, ile jeszcze przede mną nauki, aby tę całą tajemną wiedzę przyswoić.

6 komentarzy:

  1. Spring 3.x ma wbudowane Spring Java Config więc jeśli masz dość XML-a to jak najbardziej możesz całą konfiguracje umieścić w kodzie java.

    OdpowiedzUsuń
  2. CDI w Java SE => Weld SE. Swietnie sie spisuje, uzywamy go w naszym frameworku do testowania.

    OdpowiedzUsuń
  3. @pedro, pewnie, można, tylko kto z tego korzysta?! Czyżbyś sam był przykładem ;-)

    @wujek.srujek, zgadza się. Można używać każdy z kontenerów CDI poza środowiskiem serwera aplikacyjnego, ale nie ma jeszcze wspólnego, jednolitego API, które pozwoliłoby podmieniać jedynie dostawców CDI (kontenery CDI, np. Weld, OpenWebBeans czy CanDI) jak w JPA.

    OdpowiedzUsuń
  4. Jestem świeżutki jeśli chodzi o (C)DI i szukając informacji na temat sensu powstania dwóch specyfikacji w JEE6 znalazłem łopatologiczne wyjaśnienie Adama Bienia, może komuś kiedyś się przyda:

    http://java.dzone.com/articles/what-relation-betwe-there

    OdpowiedzUsuń
  5. Jacku, czy wiesz może coś o możliwości uruchomienia CDI na WAS-ie 7? Czy WebSphere 'przełknie' Welda albo OpenWebBeans wpięte w aplikację JEE5?

    OdpowiedzUsuń
  6. Nic nie wiem, ale przyczyniłeś się, abym się dowiedział. Niebawem znajdziesz odpowiedź na blogu. Zastanawiam się na ile CDI jest niezależne od funkcjonalności JEE6, której WAS7 nie oferuje, a jedynie WAS8 (obecnie w open-beta2).

    OdpowiedzUsuń