Na początek małe profanum muzyczne…

</param></param></param>

Już doszliście do siebie… to dobrze…

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

<?xml version="1.0" encoding="UTF-8"??><persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"><persistence-unit name="source-pu" transaction-type="RESOURCE_LOCAL"><provider>org.hibernate.ejb.HibernatePersistence</provider><class>pl.koziolekweb.guice.SomeEntity</class><properties><property name="hibernate.connection.url" value="jdbc:hsqldb:mem:source"></property><property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"></property><property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"></property><property name="hibernate.hbm2ddl.auto" value="create-drop"></property><property name="hibernate.show_sql" value="true"></property><property name="hibernate.format_sql" value="true"></property><property name="hibernate.connection.username" value="sa"></property><property name="hibernate.connection.password" value=""></property></properties></persistence-unit><persistence-unit name="target-pu" transaction-type="RESOURCE_LOCAL"><provider>org.hibernate.ejb.HibernatePersistence</provider><class>pl.koziolekweb.guice.SomeEntity</class><properties><property name="hibernate.connection.url" value="jdbc:hsqldb:mem:target"></property><property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"></property><property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"></property><property name="hibernate.hbm2ddl.auto" value="create-drop"></property><property name="hibernate.show_sql" value="true"></property><property name="hibernate.format_sql" value="true"></property><property name="hibernate.connection.username" value="sa"></property><property name="hibernate.connection.password" value=""></property></properties></persistence-unit></persistence>

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.