Google Guice
Koziołek
1 Informacje podstawowe
2 Pierwsze kroki
2.1 Konfiguracja
2.2 Wiązanie ad hoc
2.3 Adnotacja @Singleton
3 Podsumowanie
Informacje podstawowe
Google Guice jest biblioteką realizującą wzorzec wstrzykiwania zależności (ang. Dependency Injection DI). Podstawowymi założeniami, poza tymi związanymi z DI, jest maksymalne wykorzystanie mechanizmów języka Java oraz pełne uniezależnienie kodu biznesowego od biblioteki. Realizowane jest to w dwójnasób.
- Guice jest zgodne z JSR-330 dzięki czemu, można wykorzystać tylko standardowe elementy API i swobodnie wymienić implementację.
- Cała konfiguracja Guice odbywa się w klasach Java. Dzięki temu już na etapie kompilacji może zostać sprawdzona m.in. poprawność nazw klas czy prawidłowość powiązań.
Dodatkowo takie podejście ułatwia testowanie kodu oraz jego późniejsze utrzymanie.
Pierwsze kroki
Przyjrzyjmy się prostemu problemowi. Nasz system reprezentowany przez klasę MySystem
:
package net.programmers4.guice;
public class MySystem {
private BussinessInterface bussinessInterface;
public void callBusinessInterface() {
bussinessInterface.printMessage();
}
}
Wykorzystuje interfejs BussinessInterface
wywołując jego metodę printMessage
. Interfejs ten może być implementowany na wiele różnych sposobów. Przykładowo może być dostarczany jako usługa zewnętrzna (web service, RMI, etc.), implementowany w ramach naszego systemu albo być dostarczany w osobnej bibliotece.
Otwartą kwestią pozostaje powiązanie klasy implementującej ten interfejs z naszym systemem. W klasycznym, bez DI, rozwiązaniu samodzielnie stworzylibyśmy obiekt i ustawili go w naszym systemie za pomocą settera albo konstruktora. Jednakże przy dużej ilości obiektów, jeżeli zajdzie konieczność zmiany implementacji, to musimy też samodzielnie zmienić kod, wszędzie tam, gdzie wywołujemy konstruktory. To może pociągnąć za sobą konieczność kolejnych zmian w kodzie itd.
W naszym przypadku dodatkową trudność stanowi brak bezpośredniego dostępu do pola. Nie jest on możliwy bez użycia refleksji. Użyjmy zatem Guice. Na tym etapie wymagać będzie to tylko jednej zmiany w kodzie. Nowa wersja klasy MySystem
:
package net.programmers4.guice;
import javax.inject.Inject;
public class MySystem {
@Inject
private BussinessInterface bussinessInterface;
public void callBusinessInterface() {
bussinessInterface.printMessage();
}
}
Adnotacja @Inject
pochodzi z JSR-330. Jeszcze szybki rzut oka na klasę zawierającą metodę main
:
package net.programmers4.guice;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class App {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new MyFirstModule());
MySystem mySystemInstance = injector.getInstance(MySystem.class);
mySystemInstance.callBusinessInterface();
}
}
Na początku tworzymy Injector
, czyli specjalny obiekt odpowiedzialny za zarządzanie całym kontenerem DI. Następnie pobieramy z niego instancję MySystem
i na koniec wywołujemy interfejs biznesowy.
Konfiguracja
Guice dzieli aplikację na moduły. Modułem jest klasa implementująca interfejs Module
, ale w praktyce, zamiast implementować ten interfejs, rozszerza się jedną z kilku klas dostarczonych wraz z biblioteką (AbstractModule
, PrivateModule
). W powyższym przypadku modułem głównym jest MyFirstModule
:
package net.programmers4.guice;
import com.google.inject.AbstractModule;
import com.google.inject.Scopes;
public class MyFirstModule extends AbstractModule {
/**
* Konfiguracja modułu.
*/
protected void configure() {
bind(BussinessInterface.class).to(BussinessInterfaceImplA.class).in(
Scopes.SINGLETON);
}
}
Metoda bind
zwraca obiekt zawierający informację o powiązaniu (ang. binding) interfejsu z parametru z klasą. Mapowanie interfejs-klasa odbywa się tu za pomocą metody to
.
Metoda in
reprezentuje zakres, który w tym przypadku jest singletonem. Domyślnym zakresem w Guice jest NO_SCOPE
co oznacza, że za każdym razem, gdy należy wstrzyknąć obiekt, jest tworzona nowa instancja.
Wiązanie ad hoc
Warto zauważyć, że w konfiguracji nie podajemy informacji o klasie MySystem
. W przypadku klas, które pobieramy bezpośrednio i nie wiążemy ich z konkretnym interfejsem, nie ma konieczności konfigurowania ich w ramach modułu. Muszą one jednak spełniać kilka warunków:
- Muszą mieć nieprywatny konstruktor domyślny. Jest to równoważne z brakiem konstruktora.
- Jeżeli nie posiadają konstruktora domyślnego, to muszą mieć dokładnie jeden konstruktor oznaczony adnotacją
@Inject
, a zależności podawane w parametrach tego konstruktora muszą być skonfigurowane albo być klasami.
Wiązanie tego typu ma domyślną konfigurację zakresu.
Adnotacja @Singleton
Nasz system reprezentowany przez klasę MySystem
powinien występować w środowisku jako singleton. Jak już wcześniej pokazano, można to osiągnąć poprzez dodanie odpowiedniej konfiguracji w definicji modułu. Jest to dobre rozwiązanie, jeżeli chcemy mieć wybór pomiędzy różnymi zakresami, a singleton nie jest zakresem domyślnym. Jeżeli chcemy, by domyślnym zasięgiem był singleton, należy w definicji klasy dodać adnotację @Singleton
:
package net.programmers4.guice;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class MySystem {
@Inject
private BussinessInterface bussinessInterface;
public void callBusinessInterface() {
bussinessInterface.printMessage();
}
}
Oczywiście można zmienić konfigurację domyślną poprzez przesłonięcie jej w definicji modułu:
bind(MySystem.class).in(Scopes.NO_SCOPE);
Podsumowanie
Guice jest bardzo prostym i lekkim rozwiązaniem. W innych artykułach w tym dziale przedstawione są różne elementy API Guice oraz sposoby ich użycia.
Przewagą Guice nad innymi rozwiązaniami tego typu jest niewątpliwie prostota oraz użycie API języka bez konieczności tworzenia osobnych plików XML.