Dáte si džus? Máme Google Guice.
Pod křídly, která poskytla firma Google, vzniknul velice zajímavý projekt inversion of control frameworku postaveného na vlastnostech Javy 5 — anotacích a generikách. Framework nese název Guice, celým názvem Google Guice a vzniknul na základě potřeb aplikace AdWords. Další zajímavostí je, že se Guice hrdě hlásí k Spring frameworku.
V Google Guice IoC se vazby mezi objekty vyjadřují čistě pomocí kódu, slouží k tomu speciální API. Jeho prvním stavebním kamenem je takzvaný modul, ve kterém se popisuje základní mapování. Druhým kamenem je anotace @Inject pomocí, které se frameworku řekne, že má dojít k vložení závislosti.
Mějme třídu Car, která používá implementaci Engine, ta může být implementována různými typy motorů.
public class Car {
public Engine engine;
public void start(){
engine.start();
}
@Inject
public void setEngine(Engine engine) {
this.engine = engine;
}
}
public class BasicModule extends AbstractModule {
@Override
protected void configure() {
bind(Engine.class).to(EngineHDi.class);
}
}
Z pohledu Guice máme modul (potomek specifické Guice třídy — ), který umožňuje pomocí API definovat mapování mezi rozhraním Engine a jeho implementací. V našem případě EngineTDi. Car obsahuje anotaci @Inject, kterou v tomto případě říka Guice frameworku: Až budeš požádán o vytvoření třídy Car, vlož podle mapování danou Engine implementaci.
Aby mohl Guice fungovat musíme ho samozřejmě zinicializovat a pak požádat o danou třídu.
public class Main {
public static void main(String[] args) {
BasicModule module = new BasicModule();
Injector injector = Guice.createInjector(module);
Car car = injector.getInstance(Car.class);
car.start();
}
}
Jak vidíte celé IoC se řídí čistě pomocí kódu, není nutné žádné XML. To má za výhodu, že není potřeba speciální vývojová rozšíření pro správu kódu. Díky tomu funguje v IDE například bezproblémově refaktorizace.
Nyní si představme situaci, že bychom chtěli mít pro Engine dvě různé implementace (EngineTDi a EngineHDi) a každou bychom chtěli vkládat podle nějakého klíče. Rozlišovací klíč může být v případě Guice anotace.
Pro rozlišení motorů jsem se rozhodl vyrobit si anotaci @Octavia.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@BindingAnnotation
public @interface Octavia {
}
Nyní v mapování, které definuji v modulu, nastavím pro Engine implementaci EngineTDi rozlišenou anotací @Octavia.
public class BasicModule extends AbstractModule {
@Override
protected void configure() {
bind(Engine.class).to(EngineHDi.class);
bind(Engine.class).annotatedWith(Octavia.class).to(EngineTDi.class);
}
}
Pro ilustraci jsem vytvořil třídu Skoda, která je potomkem třídy Car. To důležité na kódu této třídy je použití anotace @Octavia, která říká ve spojitosti s anotací @Inject, že Guice má podle mapování vytvořit EngineTDi.
public class Skoda extends Car {
private Color color;
@Inject
public void setEngine(@Octavia Engine engine) {
this.engine = engine;
}
}
Při volání Skoda skoda = injector.getInstance(Skoda.class); pak IoC framework skutečně nastaví EngineTDi.
Další vastností tohoto IoC frameworku je mapování konstant. Opět si budeme demonstrovat na našem příkladu s auty. Mějme vyčtový typ Color.
public enum Color {
RED, BLUE, BLACK, WHITE;
}
Nyný si libovolnou barvu namapujeme jako konstantu a tuto konstantu necháme Guicem vložit. Pro rozlišení jednotlivých konstant použijeme anotaci (můžeme použít i typ konstanty).
public class BasicModule extends AbstractModule {
@Override
protected void configure() {
bind(Engine.class).to(EngineHDi.class);
bind(Engine.class).annotatedWith(Octavia.class).to(EngineTDi.class);
bindConstant().annotatedWith(RedColor.class).to(Color.RED);
}
}
Následuje rozšířený kód třidy Skoda o barvu. Guice se opět dozví o nutnosti přiřazení závislosti pomoci anotace @Inject. Kód ilustruje, že Guice nemusí pro vkládání závislostí používat pouze metody se setter konvenci, ale libovolné metody. Lze anotovat i jednotlivé proměnné, tomu Guice samozřejmě také rozumí.
public class Skoda extends Car {
private Color color;
@Inject
public void setEngine(@Octavia Engine engine) {
this.engine = engine;
}
@Inject
public void color(@RedColor Color color){
this.color = color;
}
public Color getColor(){
return color;
}
}
Guice IoC má další vlastnosti, některé z nich zmíním pouze bodově.
- scopes — pro každé mapování můžeme určit, jestli bude objekt z pohledu frameworku Singleton (jedna instance pro všechna volání) nebo Prototyp (kolik volání tolik instancí)
- providers — umožňuje delegovat vytvoření daného obektu na konkrétní třídu (factory). To se hodí v případě kódu, do kterého nemůžeme dodat anotace např. knihovny třetích stran.
- lazy inicializace — inicializace objektů je odložena na první požadavek
- interceptory — umožňuje namapovat třídu (interceptor) na volání metody, která má danou anotaci
Google Guice o sobě hrdě prohlašuje, že se jedna o “lehkotonážní” IoC framework a má pravdu. Práce s ním je opravdu velice jednoduchá a přímočará. Pokud hledáte srovnání se Spring IoC, tak mohu sloužit následujícím linkem do Guice wiki SpringComparison.
Z mého osobního pohledu přináši Guice svěží vítr, nicméně konfiguraci pomocí anotací i pomocí kódu Spring zvládá taktéž viz má prezentace na CZJUG. Jednou z užitečných vlastností, které Spring nabízí navíc jsou takzvané BeanPostProcessory, které umožňují modifikovat danou beanu, před tím, než jí kontejner vystaví. Samozřejmě v budoucnosti zde budou (možná již jsou) řešení, která umožní Spring a Guice používat dohromady.
Pokud hledáte pouze IoC framework pak Guice stojí za otestování a prozkoumání všech jeho možnosti. Další pohled na Guice nabízí oživený Jablok rukou Pavla Kolesnikova v článku s příznačným názvem Google Guice.
Můžete si stáhnout zdrojový kód výše uvedených příkladů.
Související články
Související články
Související epizody CZ Podcastu
- #3 · Java 6 SE, Vývoj webových aplikací
Dagi a Filemon o novinkách v Javě 6 a webových frameworcích.
- #1 · Vývojová prostředí v Javě
První díl CZ Podcastu se věnuje vývojovým prostředím pro Javu.