Kdybychom žili v ideálním programátorském světě, nebylo by nástrojů, jako je Spring Framework, zapotřebí. Protože v takovém světě nežijeme a jsme tedy postaveni před reálné problémy, můžeme s povděkem kvitovat služby, které nám framework Spring poskytuje. Spring je spojen s přívlastkem J2EE lightweight kontejner. Ona dvě magická E možná vyvolají u leckterých vývojářů lehké pochyby o jeho využití, ale opak je pravdou. Spring nabízí celou škálu služeb a rozhodně platí, že J2EE != EJB.

Motivace

Základní motivací pro vývoj systému Spring Framework bylo a je usnadnění vývoje v oblasti enterprise aplikací. Pokud vás fráze „enterprise aplikace“ leká stejně jako ortodoxního muslima pravá jihočeská zabijačka, mohu vás uklidnit. Enterprise aplikace je jedním z nejfrekventovanějších buzz-words, na které můžete v současnosti narazit. Pojďme si nastínit, co znamená ono usnadnění vývoje.

  • Pomoc při odstranění těsných programových vazeb jednotlivých POJO objektů a vrstev za pomoci návrhového vzoru Inversion of Kontrol.
  • Možnost volby implementace (EJB, POJO) business vrstvy pro aplikační architekturu a ne naopak (tedy aby architektura předepisovala implementaci).
  • Řešení různých aplikačních domén bez nutnosti použití EJB, například transakční zpracování, podpora pro remoting business vrstvy formou webových služeb či RMI.
  • Podpora implementace komponent pro přístup k datům, ať již formou přímého JDBC či ORM (object-relation mapping) technologií a nástrojů jako Hibernate, TopLink, iBatis nebo JDO.
  • Odstranění závislosti na roztroušených konfiguracích a pracného dohledávání jejich významu.
  • Abstrakce vedoucí ke zjednodušenému používání dalších části J2EE, jako například JMS, JMX, JavaMail, JDBC, JCA nebo JNDI.
  • Usnadnění používání a psaní unit testů.
  • Správa a konfigurační management business komponent.

Pokud se bavíme o usnadnění vývoje enterprise aplikací, dříve nebo později narazíme na magické zaklínadlo v podobě technologie EJB (Enterprise Java Beans). Můžeme si to říci celkem bez okolků, Spring je tu také jako alternativu k EJB. A proč vlastně hledat alternativu k EJB?

  • I přes četné změny EJB specifikace navazuje na verzi 1.0 z roku 1998, která odrážela tehdejší stav včetně EJB modelu.
  • Přílišná komplexnost EJB, pro jednu komponentu potřebujeme čtyři soubory – component interface (Home nebo Remote), business interface, vlastní implementaci a deployment descriptor. Home a remote rozhraní přitom nemají žádnou souvislost s business funkcí dané komponenty.
  • Invazivnost EJB, business logika běží pouze uvnitř EJB kontejneru.
  • Problém s unit testy, viz invazivnost.
  • Špatná přenositelnost komponent díky proprietárním deployment descriptorům v rámci jednotlivých kontejnerů.

Samozřejmě EJB má své místo na slunci, například tam, kde potřebujeme distribuované transakce. Na druhou stranu, i vývojáři používající EJB kopírují pravidlo 80 ku 20. Tedy – 80 % vývojářů bohatě vystačí s dvaceti procenty toho, co nabízí EJB.

Spring versus EJB

Pomalu nám ve vzduchu visí otázka porovnání Springu a EJB. Toto porovnání je ovšem trochu přitažené za vlasy, neboť se míchají jablka a hrušky. Především si musíme uvědomit, že EJB je specifikace spadající do větší množiny nazvané J2EE. Na druhou stranu můžeme porovnat možnosti v rámci jednotlivých aplikačních domén (transakce, bezpečnost, remoting, persistence mechanismus a podobně), před které jsme postaveni při použití EJB či Springu. Nadto je třeba poznamenat, že i v případě použití EJB není Spring nijak diskvalifikován. Spring se může velice dobře využít i pro zjednodušení práce s EJB.

Doména EJB Spring
Transakční zpracování
  • deklarativní (metoda, třída, použití zástupného znaku *), formou CMT via deployment descriptor
  • programové, přímým využitím JTA
  • nativní podpora distribuovaných transakcí díky JTA
  • transkace lze aplikovat pouze na EJB komponenty
  • nelze deklarativně ovlivnit rollback chování
  • přímá podpora transakcí přes specifické prostředí Hibernate, iBatis, JTA, JDBC, JDO
  • deklarativní (metoda, třída, použití zástupného znaku * nebo regexp), v konfiguraci aplikačního kontextu Springu nebo přes metadata na úrovni kódu
  • deklarativní definice rollback chování
  • programové, dvě možnosti – přes rozhraní TransactionTemplate nebo PlatformTransactionManager s odstíněním od konkrétního transakčního prostředí
  • nepodporuje přímo distribuované transakce, musí se využít podpora JTA
Persistence mechanismus
  • BMP, CMP
  • podpora pro integraci JDBC, Hibernate, iBatis, JDO, Toplink, Apache OJB
Bezpečnost
  • deklarativní přes uživatele a role via deployment descriptor
  • bez přímé podpory
  • možnost využití frameworku Acegi pro deklarativní definici v konfiguraci aplikačního kontextu Springu nebo přes metadata na úrovní kódu
Remoting
  • podpora RMI, JAX-RPC (specifický deployment na různých kontejnerech) pro EJB komponenty
  • podpora RMI, JAX-RPC, Hessian/Burlap – formou proxy factory pro jakýkoli POJO objekt

Spring samozřejmě nabízí služby a podporu nad rámec EJB, jenom výčtem – vlastní MVC (Model View Controller) s podporou integrace různorodých technologií (JSP+JSTL+Tiles, Velocity, FreeMarker, XSLT, JasperReports), podporu integrace s MVC a web frameworky (Struts, Tapestry, JSF a WebWork), podporu AOP (Aspect Oriented Programming), podporu unit testingu, plánovaného zpracování (job scheduling) a další. Je důležité poznamenat, že pro využití Springu nepotřebujete vůbec žádný server jako v případě EJB (samozřejmě pokud nepoužijete Spring pro EJB).

EJB 3.0

Povídání kolem Springu a EJB nemůžeme zakončit beze zmínky o připravovaném EJB 3.0. Hlavním hnacím motorem pro EJB 3.0 bylo zjednodušení vývoje. S nadsázkou můžeme říct, že úspěch Springu a myšlenek, které rozvinul – Inversion of Control, POJO model s objektovou orientací bez nutnosti implementace zbytečných rozhraní nesouvisejících s business funkcí daného objektu nebo integrace s persistence frameworky (Hibernate, TopLink) – se staly předlohou pro EJB 3.0. Specifikace EJB 3.0 pomalu spěje k finální podobě a již nyní je možno otestovat preview EJB 3.0, které poskytují někteří dodavatelé aplikačních serverů, například JBoss či Oracle Application Server.

Koncept

Všechny vlastnosti a funkce Springu, o kterých jsme se zatím zmínili, lze schématicky začlenit do sedmi modulů, které jsou zobrazeny na následujícím obrázku.

Základní moduly frameworku Spring
Základní moduly frameworku Spring (plná velikost, cca 13 kB)

Jednotlivé moduly lze využívat v rámci kontejneru (nemá nic společného s EJB kontejnerem) a jeho inicializace je otázkou jedné řádky kódu. Kontejner je prostředí, ve kterém se odehrává život všech objektů, které pomocí Springu spravujeme.

Core modul

Core modul tvoří základ celého frameworku a poskytuje funkčnost (Dependency Injection) pro řízení práce celého kontejneru. Základ tohoto modulu tvoří implementace BeanFactory, která se stará o životní cyklus jednotlivých POJO objektů – vytvoření, nastavení vazeb mezi jednotlivými objekty, inicializace, poskytování objektů k použití a ukončení při zastavení kontejneru.

Context modul

Context modul zobecňuje práci s BeanFactory na úroveň registru (obdoba JNDI) a přidává funkční nadstavbu, například v podobě podpory resources bundles, JNDI a podobně. Tím nejzajímavějším je ovšem odstínění od BeanFactory. Context modul hraje roli prostředníka mezi klientským kódem a BeanFactory. Inicializace je možná mnohem transparentnějším způsobem, například servlet kontainerem či JUnit testem.

DAO modul

Zkratka DAO znamená Data Access Object a tento modul poskytuje abstraktní vrstvu pro práci s JDBC API. Umožňuje odstranění opakujícího se kódu (získání connection, vytváření statementu, iterování přes result set), kterým trpí většina aplikací pracujících s JDBC API. Samozřejmostí je podpora deklarativní definice transakcí a především exception handling převádějící java.sql.SQLException na inteligentní hierarchii běhových alias runtime výjimek.

AOP

AOP je modul implementující podporu pro aspektově orientované programování. Umožňuje separovat části kódu prolínající se celou aplikací (autorizace, logování, transakce) do takzvaných aspektů a jejich následnou aplikaci na jakýkoli POJO objekt. Využití AOP modulu se prolíná celým frameworkem a jedná se o jednu z nejsilnějších vlastností Springu.

ORM

Modul určený k podpoře integrace OR frameworků jako Hibernate nebo iBatis. Při použití tohoto modulu je samozřejmě možné využít všech dalších funkčností Springu, jako je například podpora transakcí a AOP.

Web

Modul určený k podpoře integrace s web frameworky jako Struts, WebWork a JSF implementacemi. Dále poskytuje podpůrné třídy, například pro zpracování uploadu souborů, i18n či práci s cookies.

Web MVC

Implementace MVC určená pro webové aplikace, opět je možno využít všech dalších funkčností Springu.

Použití modulů

Kdybychom chtěli postavit za pomoci Springu kompletně celou střední vrstvu J2EE aplikace, mohlo by využití jednotlivých modulů a jejich funkčnosti vypadat následovně.

Schéma J2EE aplikace plně postavené na možnostech Springu
Schéma J2EE aplikace plně postavené na možnostech Springu (plná velikost, cca 13 kB)

Na vrcholu sedí MVC modul využívající služeb Web modulu, jako validace vstupních dat, podpora pro zpracování multipart obsahu, napojení vstupních dat na Domain model aplikace nebo integrace s vhodnou view technologii pro prezentaci dat. Uprostřed leží business vrstva s definicí business logiky v POJO objektech, na této vrstvě jsou realizovány transkace pomocí AOP modulu. Business vrstva je vystavena díky podpoře remotingu jako webová služba. Data pro business logiku poskytuje Data Access vrstva řešená za pomoci ORM a DAO modulu. Celá aplikace pak může běžet jak v servletovém kontejneru typu Tomcat/Jetty, tak v plnohodnotném J2EE serveru.

Spring nemusí nutně pokrývat celou aplikaci, ale můžeme jej využít takřka v jakémkoli scénáři, například v součinnosti se Struts, EJB nebo pro tvorbu aplikace využívající JDBC. Stejně tak není zapotřebí k využití Springu používat Tomcat. Spring, respektive BeanFactory lze nastartovat stejně dobře programově a pohodlnou cestou. Nevěříte? Tak se podívejte na třídu, kterou používám pro integrační testy a která se postará o inicializaci kontextu (jednotlivé testy si pak berou z contextu patřičné objekty k otestování).

(Pozn. aut.: Spring v současné verzi poskytuje abstraktní třídu pro JUnit testy, která odesílá zde uvedený helper do propadliště dějin. Tedy alespoň z pohledu JUnit testů, jinak je samozřejmě použitelný.)

  /**
   * Pomocná třída pro testovací objekty. Loaduje testovací aplikační
   * kontext Springu a ten poskytuje pro využítí v unit testech.
   *
   */
  public class SpringHelper {
    private static ApplicationContext ac =
      new FileSystemXmlApplicationContext(„config/spring/spring-testcontext.xml“);
  
    public static ApplicationContext getAppContext(){
      return ac;
    }
  }

Spring v akci

Nemělo by význam v tomto seznámení zabíhat do jednotlivých ukázek bez smysluplného cíle a přitom se snažit o demonstraci síly Springu. Mnohem zajímavější bude ukázat tvorbu konkrétní webové aplikace pomocí Springu, a to s různými variacemi na úrovni jednotlivých vrstev (nahrazení Spring MVC za Struts , změna implementace datové vrstvy z JDBC na Hibernate). Rád bych, abychom nezůstávali pouze u Springu, takže pokud budeme probírat datovou vrstvu, určitě si vysvětlíme i návrhový vzor Data Access Object. Pokud se budeme bavit o Hibernate, určitě si probereme ORM přístup, stejně tak si osvětlíme základy aspektově orientovaného programování.

Definice termínů

POJO

Termín POJO je zkratkou Plain Old Java Objects – volně přeloženo jako „staré dobré Java objekty“. Termín POJO formulovali Martin Fowler, Rebbecca Parsons a >Josh MacKenzie. Označuje objekty, které nejsou svázané technologicky specifickým rozhraním. Jediné rozhraní těchto objektů je definováno business službami, které poskytují.

  public inteface VypujceneKnihy {
    public List getSeznamVypujcenychKnih(Date obdobi);
    public List getSeznamVypujcenychKnih(String userId);
  }

  public class Knihovna implements VypujceneKnihy {
    public List getSeznamVypujcenychKnih(Date obdobi){
      …
    }
    public List getSeznamVypujcenychKnih(String userId){
      …
    }
  }

Inversion of Control

Vzor Inversion of Control (IoC) řeší těsné vazby mezi aplikačními objekty, které jsou těsně svázány ve zdrojovém kódu. Řekněme, že máme objekt „Knihovna“, který využívá služeb objektů „Vypujcky“ a „Registr knih“. Instanciování a inicializace objektů „Vypujcky“ a „Registr knih“ je řešena přímo v objektu „Knihovna“.

  public class Knihovna implements VypujceneKnihy{
    private Vypujcky vypujcky;
    private Registr registr;
    public Knihovna(){
      vypujcky = new Vypujcky();
      registr = new LokalniRegistrKnih();
      registr.setDataSource(„foo.xml“);
      registr.initialize();
    }
    public List getSeznamVypujcenychKnih(Date obdobi){
      //prace s objketem vypujcky a registr
    }
  }

Těsná vazba objektu „Knihovna“ na objekty „Vypujcky“ a „LokalniRegistrKnih“ má za následek, že „Knihovna“ musí instancovat konkrétní třídy a navíc je posléze zinicializovat (pokud je to potřeba). První problém nastává při inicializaci, neboť objekt „Knihovna“ musí správně zinicializovat objekt „LokalniRegistrKnih“, tedy nastavit zdroj dat, a poté zavolat inicializační metodu. Další problém je ve výběru konkrétních instancí, viz „LokalniRegistrKnih“. V případě, že bychom chtěli zaměnit „LokalniRegistrKnih“ například za „RozsirenyRegistrKnih“, museli bychom kód změnit a znovu zkompilovat.

Vzor Inversion of Control přenáší odpovědnost za vytváření, inicializaci a provázání objektů ven z aplikačního kódu, na takzvaný IoC kontejner. Objekty mají pouze povinnost poskytnout rozhraní pro vložení potřebných objektů z vnějšku.

  public class Knihovna implements VypujceneKnihy{
    private Vypujcky vypujcky;
    private Registr registr;
    public Knihovna(){
    }

    public List getSeznamVypujcenychKnih(Date obdobi){
      //prace s objketem vypujcky a registr
    }
    public void setVypujcky(Vypujcky vypujcky){
      this.vypujcky = vypujcky;
    }
    public void setRegistr(Registr registr){
      this.registr = registr;
    }
  }

Dependency Injection

Takzvaná Dependency Injection je speciální případ Inversion of Control (IoC generický termín) a řeší vlastní způsob vložení (spojení) potřebných objektů. Existují tři základní způsoby vložení.

Setter injection
Vložení se provádí pomocí klasických setteru, čili setovacích metod známých například z modelu JavaBean. Toto je jedna z možností, kterou poskytuje framework Spring.
Constructor injection
Vložení se provádí přes konstruktor, používá ji framework PicoKontejner (Spring opět podporuje).
Interface injection
Vložení se provádí skrze definované rozhraní, typickým představitelem je framework Avalon.

Diskuse se točí kolem toho, jaký způsob zvolit. Injekce skrze rozhraní stojí trošku na okraji a tak se především diskutuje, jestli je lepší použít settery nebo konstruktor. Constructor injection dovoluje jednoduše vyžadovat, aby byla implementace rozhraní nastavena při konstrukci vlastního objektu.

  public class Knihovna implements VypujceneKnihy{
    private Vypujcky vypujcky;
    private Registr registr;
    public Knihovna(Vypujcky vypujcky, Registr registr){
      this.vypujcky = vypujcky;
      this.registr = registr;
    }
    public List getSeznamVypujcenychKnih(Date obdobi){
      //prace s objketem vypujcky a registr
    }
  }

Setter injection má obecně menší dopad na kód v případě, že běží mimo IoC kontejner (typicky pro unit testy). Nevýhodou je, že je možné vytvořit instanci třídy bez nastavení závislostí.

Odkazy a zdroje

(Pozn. aut.: Za odbornou korekturu děkuji Janu Macháčkovi, spoluautorovi knihy Pro Spring.)

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

1 Příspěvěk v diskuzi

  1. možná by bylo lepší dávat vysvětlivky ke zkratkám (ikdyž jsou možná všeobecně známé) na začátku článku a ne až v půlce…

Odpovědět