Na początek małe profanum muzyczne…
Już doszliście do siebie… to dobrze…
Contents
Słowo wstępne
Na spotkaniu WJUGa, na którym wraz z Wojtkiem Ebertowskim omawialiśmy Guice Paweł Szklarz zarzucił temat „Guice to ma a Spring nie” na przykładzie modułów prywatnych. Rzecz jest to o tyle ciekawa, że świetnie obrazuje podstawową różnicę pomiędzy Guice, a Springiem. Sposób konfiguracji.
Ogólnie rzecz ujmując. W springu wszystko co zostanie użyte w ramach taga bean jest przetwarzane tylko w kontekście tego taga. Jeżeli zatem macie dwa beany springowe implementujące ten sam interfejs i zależne od jakiegoś tam, ale identycznego, interfejsu w dwóch różnych implementacjach to możecie skonfigurować wszystko w osobnych tagach bean. Zależności uczynić anonimowymi (nie ustawiać id) i generalnie pozbyć się problemu. W Guice nie za bardzo idzie tak zrobić, ponieważ chcąc uzyskać jak najogólniejsze łączenie interfejsu z implementacją musimy takie mapowanie upublicznić. Powoduje to, że nie można dodać kolejnego identycznego mapowania.
Przykład z życia
Potrzebujemy w ramach aplikacji odwoływać się do dwóch identycznych baz danych. Te same mapowania, te same operacje, itd. tylko inne adresy. W Springu stworzylibyśmy klasę i skonfigurowali ja jako dwa niezależne beany, każdy w własną konfiguracją. W Guice taka sztuczka nie przejedzie ponieważ konfiguracja JpaPersistModule nie pozwala na wykorzystanie wielu jednostek utrwalania. Dodatkowo nie idzie tego skonfigurować. Z pomocą przychodzą tu moduły prywatne.
Moduł prywatny kto zacz
Moduł prywatny jest to specjalna implementacja modułu Guice, która w trakcie tworzenia w danym injectorze tworzy injector pochodny i do niego dowiązuje swoją konfigurację. W ten sposób konfiguracja jest niewidoczna w nadrzędnym injectorze, a tym samym można stworzyć wiele identycznych konfiguracji, które nie będą się wzajemnie widzieć. Można oczywiście pewne elementy konfiguracji upublicznić co pozwala na np. użycie dwóch identycznych jednostek utrwalania w ramach jednego projektu.
Przykładzik
Zadanie przepisać tabelę z jednej bazy do drugiej.
Na początek konfiguracja baz danych. Wykorzystuję tu dwie instancje HSQLDB umieszczone w pamięci. Jedyny problem z tym związany to konieczność dodania danych do źródła za pomocą DAO. Jak chcesz to przerzuć się na postgresa i wykorzystaj DBUnit.
Listing 1. konfiguracja baz – persistence.xml
org.hibernate.ejb.HibernatePersistence pl.koziolekweb.guice.SomeEntity org.hibernate.ejb.HibernatePersistence pl.koziolekweb.guice.SomeEntity
Następnie interfejs DAO:
Listing 2. DAO
package pl.koziolekweb.guice; import java.util.Collection; public interface SomeEntityDao { void saveAll(Collection<SomeEntity> someEntities); Collection<SomeEntity> getAll(); }
Implementacja
Listing 3. implementacja DAO
package pl.koziolekweb.guice; import java.util.Collection; import javax.inject.Inject; import javax.persistence.EntityManager; import com.google.inject.persist.Transactional; public class SomeEntityDaoImpl implements SomeEntityDao { @Inject private EntityManager em; @Transactional @Override public void saveAll(Collection<SomeEntity> someEntities) { for (SomeEntity se : someEntities) em.merge(se); em.flush(); } @Transactional @Override public Collection<SomeEntity> getAll() { return em.createQuery("Select se From SomeEntity se", SomeEntity.class).getResultList(); } }
oraz nasza encja
Listing 4. SomeEntity
package pl.koziolekweb.guice; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.SequenceGenerator; @Entity public class SomeEntity { @SequenceGenerator(name = "Gen", sequenceName = "Seq") @GeneratedValue(generator = "Gen") @Id public Long id; public String name; public SomeEntity() { } public SomeEntity(String name) { this.name = name; } }
Jak widać nie ma tu nic niezwykłego.
Aplikacja
Kolejnym elementem jest aplikacja przepisująca dane. Klasa posiada ona dwa pola reprezentujące dao powiązane z różnymi PU.
Listing 5. Wybitnie skomplikowana klasaApp
package pl.koziolekweb.guice; import java.util.LinkedList; import java.util.List; import javax.inject.Inject; import javax.inject.Named; import com.google.inject.Guice; import com.google.inject.Injector; public class App { @Inject @Named("source") private SomeEntityDao source; @Inject @Named("target") private SomeEntityDao target; public static void main(String[] args) { Injector injector = Guice.createInjector(new MainModule()); App instance = injector.getInstance(App.class); instance.prepare(); instance.verify(); instance.copy(); instance.verify(); } public void verify() { System.out.println(source.getAll().size()); System.out.println(target.getAll().size()); } public void copy() { target.saveAll(source.getAll()); } public void prepare() { List<SomeEntity> data = new LinkedList<SomeEntity>(); data.add(new SomeEntity("A")); data.add(new SomeEntity("B")); data.add(new SomeEntity("C")); source.saveAll(data); } }
Jak widać instalujemy tylko jeden moduł – MainModule. Przyjrzyjmy się mu 🙂
Listing 6. główny moduł aplikacji
package pl.koziolekweb.guice; import com.google.inject.AbstractModule; public class MainModule extends AbstractModule { @Override protected void configure() { install(new DaoModule("source-pu", "source")); install(new DaoModule("target-pu", "target")); } }
Nie ma tu nic nadzwyczajnego. Instalujemy dwa moduły DaoModule. Przyjrzyjmy się tej klasie (zaczyna się robić wpis ginekologiczno-pornograficzny wnikamy głębiej).
Listing 7. Moduł zarządzający DAO
package pl.koziolekweb.guice; import com.google.inject.PrivateModule; import com.google.inject.name.Names; import com.google.inject.persist.jpa.JpaPersistModule; public class DaoModule extends PrivateModule { private String jpaUnit; private String emMarker; public DaoModule(String jpaUnit, String emMarker) { super(); this.jpaUnit = jpaUnit; this.emMarker = emMarker ; } @Override protected void configure() { install(new JpaPersistModule(jpaUnit)); bind(DaoInitializer.class).asEagerSingleton(); bind(SomeEntityDao.class).annotatedWith(Names.named(emMarker)).to(SomeEntityDaoImpl.class); expose(SomeEntityDao.class).annotatedWith(Names.named(emMarker)); } }
Tu zaczyna być ciekawie. Zanim omówię co i jak to dla porządku klasa DaoInitializer:
Listing 8. Klasa DaoInitializer
package pl.koziolekweb.guice; import javax.inject.Inject; import com.google.inject.persist.PersistService; public class DaoInitializer { @Inject public DaoInitializer(PersistService service) { System.out.println(service.toString()); service.start(); } }
ale co się dzieje
Na listingu 8 mamy sobie klasę – moduł guice, która zamiast AbstractModule rozszerza PrivateModule. W konstruktorze przyjmuje ona sobie dwa parametry. Pierwszy to nazwa PU z pliku konfiguracyjnego, a drugi to nazwa pod jaką będziemy udostępniać DAO. Generalnie mamy tu dość dużą swobodę. Można przyjąć, że pola trzymające DAO muszą mieć taką samą nazwę (w sensie adnotacji @Named) jak używany PU. Można dać opcję na własną adnotację. Można przyjąć, że nazwa podana w kodzie ma odpowiednik w xmlu z jakimś przedrostkiem, przyrostkiem, wrostkiem, wyrostkiem czy innym tego typu tworem (brak ci inwencji w nazewnictwie – sięgnij po podręcznik do dermatologii, na pewno coś znajdziesz).
Następnie jest sobie metoda configure, a w niej instalujemy w pierwszej kolejności moduł do obsługi JPA. Następnie tworzymy i inicjujemy (użycie asEagerSingleton) obiekt DaoInitializer, który uruchamia nam usługę JPA. Teraz możemy już powiązać interfejs i implementację DAO koniecznie wykorzystując tu informację, że pole jest adnotowane. Ostatnim akordem tej zabawy jest wywołanie metody expose, która udostępnia definicję naszej konfiguracji innym modułom (umieszcza ją w nadrzędnym injectorze). Co ważne wszystko co znajduje się w tym module jest odseparowane od świata. Zatem nie ma możliwości by nadpisać sobie konfigurację w innym module.
Uruchamiamy…
Listing 9. uruchomienie aplikacji
com.google.inject.persist.jpa.JpaPersistService@77158a com.google.inject.persist.jpa.JpaPersistService@a7c45e Hibernate: insert into SomeEntity (id, name) values (null, ?) Hibernate: call identity() Hibernate: insert into SomeEntity (id, name) values (null, ?) Hibernate: call identity() Hibernate: insert into SomeEntity (id, name) values (null, ?) Hibernate: call identity() Hibernate: select someentity0_.id as id0_, someentity0_.name as name0_ from SomeEntity someentity0_ 3 Hibernate: select someentity0_.id as id2_, someentity0_.name as name2_ from SomeEntity someentity0_ 0 Hibernate: select someentity0_.id as id0_, someentity0_.name as name0_ from SomeEntity someentity0_ Hibernate: select someentity0_.id as id2_0_, someentity0_.name as name2_0_ from SomeEntity someentity0_ where someentity0_.id=? Hibernate: insert into SomeEntity (id, name) values (null, ?) Hibernate: call identity() Hibernate: select someentity0_.id as id2_0_, someentity0_.name as name2_0_ from SomeEntity someentity0_ where someentity0_.id=? Hibernate: insert into SomeEntity (id, name) values (null, ?) Hibernate: call identity() Hibernate: select someentity0_.id as id2_0_, someentity0_.name as name2_0_ from SomeEntity someentity0_ where someentity0_.id=? Hibernate: insert into SomeEntity (id, name) values (null, ?) Hibernate: call identity() Hibernate: select someentity0_.id as id0_, someentity0_.name as name0_ from SomeEntity someentity0_ 3 Hibernate: select someentity0_.id as id2_, someentity0_.name as name2_ from SomeEntity someentity0_ 3
…i się cieszymy.
Podsumowanie
Prywatne moduły wprowadzają nam funkcjonalność, która nie jest spotykana w Springu. Jednocześnie należy zaznaczyć, że Spring reprezentuje zupełnie inne podejście do problemu konfiguracji i zakresów widoczności poszczególnych beanów. Tym samym nie można mówić o tym, które rozwiązanie jest lepsze. One są po prostu diametralnie różne.
Przyznam szczerze, że nie doczytałem posta do końca chyba ze względu już na późną porę, ale jutro w trakcie pracy na pewno go skończę. Zanim jednak mi to wyleci pewnie zainteresuje Ciebie również i to podejście: http://stackoverflow.com/questions/2684189/ioc-problem-with-multi-binding
Akurat multibinding w Guice ładnie mi się komponuje z ósmą z zasad Bay’a. Dobrze widać dlaczego warto opakowywać kolekcje i nie udostępniać „gołych” interfejsów kolekcji.