O technologii i biznesie naszym zdaniem
Posts tagged gin
Google GIN, czyli dependency injection w GWT
września 4th
Wstęp
Google GIN (GWT INjection) to stosunkowo młody projekt Google, wprowadzający do Google Web Toolkit możliwość wstrzykiwania zależności (ang. dependency injection). Oparty jest na Google Guice (o którym pisałem kilka tygodni temu) i zapewnia pewien podzbiór funkcjonalności tej biblioteki. Po co więc GIN? Potrzeba wynika wprost ze specyfiki aplikacji tworzonych w GWT, która uniemożliwia zastosowanie “typowego” frameworka do wstrzykiwania zależności. Rozwinę ten temat za chwilę.
Póki co, nie ma możliwości pobrania skompilowanej biblioteki ze strony projektu (tak jak pisałem, jest to jeszcze wczesna faza rozwoju), więc pozostaje check out z publicznego repozytorium i własnoręczna kompilacja:
svn checkout http://google-gin.googlecode.com/svn/trunk/ google-gin-read-only cd google-gin-read-only ant dist
Zbudowanego JARa (./out/dist/gin.jar) oczywiście należy dołączyć do swojego projektu.
Zaczynamy!
Mamy już dostępne klasy GIN, więc można zaczynać. W regularnym Guice, po skonfigurowaniu zależności w klasie modułu i utworzeniu Injectora, obiekty pobiera się mniej więcej tak:
RegistrationService registrationService = injector.getInstance(RegistrationService.class);
W GWT jednak powyższy kod nie zadziała. Żeby być ścisłym: nie zadziała w komponentach po stronie klienta (ang. client-side). Jak wiadomo, aplikacja GWT dzieli się na stronę klienta i stronę serwera. Po stronie serwera nie ma żadnego problemu ze wstrzykiwaniem zależności. Obiekty są instancjonowane w rzeczywistym JRE (najczęściej w kontenerze serwletów) i żyją “normalnym” Javowym życiem. Można używać Guice, Spring albo dowolnego innego rozwiązania do wstrzykiwania zależności i będzie działać. Po stronie klienta wygląda to jednak zupełnie inaczej, ponieważ kod Java jest kompilowany do JavaScript, więc JRE nie istnieje (jest jedynie w niewielkim zakresie emulowane przez bibliotekę GWT). Standardowe użycie Guice nie zadziała więc z dwóch powodów:
- Na poziomie JavaScript nie istnieją klasy.
- Guice (podobnie jak większość tego typu rozwiązań) często korzysta z mechanizmu reflection Javy, który nie jest w ogóle emulowany w GWT.
GIN stosuje więc inny sposób na zapewnienie wstrzykiwania zależności. Na początek należy zaimportować moduł GIN we własnym module GWT.
<module> ... <inherits name="com.google.gwt.inject.Inject" /> ... </module>
Teraz funkcjonalność oferowana przez GIN jest dostępna w obrębie strony klienta aplikacji GWT. Dochodzimy jednak ponownie do momentu, gdy “jakoś” trzeba w końcu te zależności pobrać. W GIN używa się do tego specjalnej wersji Injectora — Ginjectora (który jednak de facto nie jest związany relacją dziedziczenia z tym pierwszym).
public interface ApplicationGinjector extends Ginjector {
ApplicationPresenter getApplicationPresenter();
}
Ginjector powinien oferować tylko komponenty potrzebne na etapie inicjalizacji aplikacji. Przy ich pobieraniu zostaną zainicjowane ich zależności, a więc również zależności tych zależności itd. W ten sposób zostanie zbudowany cały graf obiektów, a zależności automatycznie powstrzykiwane. W tym przypadku, komponentem potrzebnym na samym początku działania aplikacji jest ApplicationPresenter, który wyświetla ekran startowy. Zależności definiuje się dokładnie tak jak w Guice, czyli za pomocą adnotacji @Inject.
Jak wiadomo, Injector potrzebuje również modułu, w którym zawarte są informacje na temat komponentów.
public class ApplicationClientModule extends AbstractGinModule {
@Override
protected void configure() {
bind(ApplicationPresenter.class);
// other bindings...
}
}
Jak widać, moduł po stronie klienta w GWT dziedziczy z AbstractGinModule. Poza tym wszystko wygląda dokładnie jak w Guice. Istotnym niuansem związanym z modułami GIN jest fakt, że w przypadku nie odnalezienia powiązania dla klasy, automatycznie wywoływana jest dla niej metoda GWT.create(), przez co niektóre rzeczy (np. asynchroniczne interfejsy usług) będą działać nawet bez odpowiedniej deklaracji w klasie modułu.
Zdefiniowany moduł należy jeszcze skojarzyć z właściwym interfejsem Ginjector za pomocą adnotacji @GinModules.
@GinModules(ApplicationClientModule.class)
public interface ApplicationGinjector extends Ginjector {
ApplicationPresenter getApplicationPresenter();
}
W ten sposób, Ginjector jest w stanie skonfigurować całą aplikację na podstawie danych zawartych w określonym module. Aby to zrobić, należy go utworzyć za pomocą wywołania GWT.create() oraz pobrać początkowe obiekty.
public final class Application implements EntryPoint {
public void onModuleLoad() {
ApplicationGinjector injector = GWT.create(ApplicationGinjector.class);
ApplicationPresenter applicationPresenter = injector.getApplicationPresenter();
applicationPresenter.go(RootPanel.get());
}
}
W ten sposób, cała procedura wstrzykiwania zależności ma miejsce już w czasie kompilacji. Użycie GIN nie jest więc związane z żadnym dodatkowym narzutem przetwarzania. (Wyszłoby na to samo, gdybyśmy konfigurowali komponenty ręcznie.)
Prawie jak Guice
Z użyciem GIN jest związanych kilka niuansów, o czym wspomniałem już na samym początku. GIN to nie jest mimo wszystko to samo co Guice, tylko dla GWT. Najbardziej istotne różnice w stosunku do Guice to:
- Zamiast typów Module i AbstractModule są GinModule i AbstractGinModule.
- Zamiast typu Injector jest Ginjector z adnotacją @GinModules.
- Niemożliwe jest użycie powiązania toInstance() i prawdopodobnie nigdy nie będzie możliwe (ze względu na to, że GIN działa w czasie kompilacji, a nie wykonania).
- Na chwilę obecną nie jest możliwe definiowanie własnych zasięgów.
- Nie ma adnotacji @ImplementedBy oraz @ProvidedBy.
- Brak obsługi zależności cyklicznych.
- Brak wsparcia dla AOP.
Warto jeszcze dodać, że istnieje sposób na współpracę z regularnym Guice za pomocą klasy GinModuleAdapter, dzięki której GinModule staje się dostępny jako zwykły Module. Można więc rozważyć utworzenie wspólnej części zależności w postaci GinModule. Ponadto, zrozumienie jak działa strona klienta w GWT na pewno pomoże uniknąć wielu potencjalnych problemów z GIN.
Podsumowanie
Jak widać, w GWT również można wygodnie wstrzykiwać zależności i to korzystając z bardzo przyjaznego API znanego z Guice. Mimo, że biblioteka ta jest jeszcze nieco niedojrzała i nie doczekała się nawet wersji 1.0, moim zdaniem warto się nią zainteresować i zastosować we własnych projektach w Google Web Toolkit. Używając jej przez ostatnie kilka miesięcy nie napotkałem żadnych problemów (w szczególności ze stabilnością), a znacząco ułatwiłem sobie pracę, rezygnując z ręcznego wstrzykiwania zależności. Myślę, że Google jeszcze nie raz zaskoczy nas różnymi ciekawymi dodatkami do GWT. Póki co, zapraszam na stronę domową projektu oraz zachęcam do własnych eksperymentów z Google GIN.