pátek 16. března 2007

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