Office week Slevový týden - Květen
Pouze tento týden sleva až 80 % na e-learning týkající se MS Office
30 % bodů zdarma na online výuku díky naší Slevové akci!

Spring - IOC Kontajner

Spring je veľmi rozšírený Java framework, ktorý obsahuje niekoľko rôznych projektov. Dalo by sa povedať, že všetky projekty spája jeden spoločný kontajner.

Inversion of Control

Spring kontajner využíva návrhový vzor Inversion of Control (IOC). Ten uvoľňuje pevné väzby medzi objektmi. Pevná väzba znamená, že trieda si sama inicializuje svoje vlastnosti (inej triedy, s ktorými má vzťah) a nedostane je z vonku.

Pevným väzbám sa snažíme vyhýbať!

public interface CarDao {}

public class CarDaoImpl implements CarDao {}

// zde mezi CarDao a CarService je pevná vazba
public class CarServiceImpl {
    private CarDao carDao = new CarDaoImpl();
}

Prečo? Vaša architektúra je príliš úzko zviazaná (podobne ako u dedičnosti) a málo flexibilné. Zmena kódu môže byť veľmi obtiažna. Trieda bez pevných väzieb sa lepšie testuje.

Ako? Princíp IOC presúva zodpovednosť za vznik väzieb na niekoho iného. V našom prípade je presunutý z programátora na framework.

Dependency injection (DI)

Tento návrhový vzor súvisí priamo s IOC. Ide o mechanizmus, kedy je do našej triedy vložená (injektovaná) inštancie inej triedy. O túto injekciu sa stará sám Framework podľa konfigurácie. Pre prehľadnosť a väčšiu flexibilitu je dobré mať oddelenú konfiguráciu od implementácie. Existujú tri typy injekcie:

Property Inject

Framework si sám nájde danú property pomocou reflexie.

@Autowired
private CarDao carDao;

Constructor Inject

Pri vytváraní pošle cez konštruktor inštancie potrebných component.

private CarDao carDao;

@Autowired
public CarServiceImpl(CarDao carDao) {
    this.carDao = carDao;
}

Setter Inject

Tento výukový obsah pomáhajú rozvíjať nasledujúce firmy, ktoré možno hľadajú práve teba!

Pri vytváraní je nahraná inštancie pomocou setter-u.

private CarDao carDao;

@Autowired
public void setCarDao(CarDao carDao) {
    this.carDao = carDao;
}

Spring

Keď už poznáme základné pojmy, poďme sa pozrieť, ako ich využíva Spring. Tu je nutné poznať dva pojmy:

Bean

Objekt, ktorý vykonáva nejakú funkčnosť (napr. Pridáva dáta do databázy, vyhľadáva ...). Bean žijú v kontajneri po celý beh aplikácie. Možno s ním pracovať v celej aplikácii. Existujú dva typy bean (scope).

  • Singleton objekt je v celej aplikácii len raz. Zakaždým, keď si povieme o daný objekt aplikačnému kontextu, dostaneme rovnakú inštanciu.
  • Prototype je podobný ako Singleton. Rozdiel je v tom, že ak si povieme o daný objekt aplikačnému kontextu, dostaneme vždy novú inštanciu.

Kontajner

V kontajneri, čiže aplikačným kontextu, žijú objekty (BEAN), ktoré tvoria funkčné jadro vašej aplikácie. Kontajner sa zavádza pri štarte aplikácie a reprezentuje ho trieda ApplicationCon­text. V celej aplikácii je len jeden a dá sa injektovať odkiaľkoľvek.

Konfigurácia aplikačného kontextu

Konfigurovať kontext môžeme pomocou XML súboru alebo pomocou Java class. Obe konfigurácie si funkčne zodpovedajú. Preferovaná cesta je Java class.

Java konfigurácia

Je realizovaná obyčajnú Java triedou, v ktorej sú použité anotácie pre tvorbu aplikačného kontextu. Je dobré, si pre konfiguračný triedy urobiť špeciálny package (configuration).

  • @Configuration vytvorí z danej triedy konfiguračný triedu
  • @Import spojí dve konfigurácie (importuje inú konfiguráciu)
  • @Bean vytvorí BEAN; typ je návratová hodnota a názov je názov metódy (ak sa nepoužije name). Možno aj zmeniť defaultnú Singleton scope (scope = DefaultScopes­.PROTOTYPE)
  • @Autowired injektuje inštanciu iné Bean
  • @ComponentScan - preskenuje zadanej package. Ak narazí na špeciálne anotácie (@Controller: prezentačná vrstva; @Service: aplikačná vrstva; @Repository: dátová vrstva) vytvorí z daných tried Beana. Užitočná vec pre rýchlu tvorbu bean.
// jedná se o konfiguraci
@Configuration
// naimportuje konfiguraci z třídy StorageConfig
@Import({StorageConfig.class})
// skenuje cz.itnetwork a tvoří beany (@Component, @Service...)
@ComponentScan("cz.itnetwork")
public class ContextConfig {
    // vytvoří beanu typu CarDao a názvem carRepository
    @Bean(name="carRepository")
    public CarDao carDao() {
        return new CarDaoImpl();
    }

    // vytvoří beanu CarService a injektuje ji CarDao (CarRepository)
    @Bean
    @Autowired
    public CarService carService(CarDao carDao) {
        return new CarServiceImpl(carDao);
    }
}
XML konfigurácie

Je reprezentovaná XML súborom. Konfigurácia sa musí nachádzať v Resources a byť na CLASSPATH.

  • <Bean id = "..." class = "..."> vytvorí Bean
  • <Import resource = "..." /> import iné konfigurácie
  • <Context: component-scan base-package = "..." /> skenovanie package
Použitia kontajnera

Pre prácu s aplikačným kontextom slúži Beana ApplicationContext. Táto bean má metódu getBean (), pomocou ktorej získate akúkoľvek Beana z kontajnera.

@Configuration
public class ContextConfig {
    @Bean
    public NameStrategy nameStrategy() {
        return new NameStrategyImpl();
    }

    ...
}

public class UpdateFactoryImpl implements UpdateFactory {
    // zisk pristupu ke kontejneru
    @Autowired
    private ApplicationContext applicationContext;

    public Strategy getStrategy(Change change) {
        if (change.isChangeName()) {
        // vytažení beany NameStrategy
                return applicationContext.getBean(NameStrategy.class);
        }

    ...

        return null;
    }
}

Príklad je výťažok kódu, kde je využitý návrhový vzor Factory. Podľa zmeny (change) sa rozhoduje ktorou stratégiu má factory vytvoriť (vytiahnuť z aplikačného kontextu). V našom prípade sa jedná o NameStrategy.

Testovanie s mockito

Ak nepoznáte mockito pozrite sa na tento článok. Z článku sa dozviete, že môžete injektované Bean namockovat (@Mock) a sledovať, či boli v teste použité.

Je dobré otestovať tiež, že sa vám správne zostaví aplikačný kontext (inicializuje sa kontajner). Tu je možné využiť metóda ApplicationCon­text.getBean ();

Rady

Je čitateľnejší a flexibilnejšie pokiaľ oddelíte konfiguráciu aplikačného kontexte od implementácie jednotlivých tried.

Výrazná výhoda je v tom, že ak budete chcieť vymeniť framework (napr. Spring za java EE), nie je to tak bolestivé. Stačí zahodiť starú konfiguráciu a vytvoriť novú.

class CarDaoImpl implement CarDao {}
public class CarServiceImpl implements CarService {
    private CarDao carDao;

    CarServiceImpl(CarDao carDao) {
        this.carDao = carDao;
    }
}

@Configuration
public class ContextConfiguration {
    @Bean
    public CarDao carDao() {
        return new CarDaoImpl();
    }

    @Bean
    @Autowired
    public CarService carService(CarDao carDao) {
        return new CarServiceImpl(carDao);
    }
}

Ako je zrejmé z príkladu, trieda CarServiceImpl využíva CarDao. Neobsahuje však žiadnu konfiguráciu, žiadne anotácie @Autowired) a ani pevnú väzbu.

Konfigurácia je vykonávaná v konfiguračnej triede (ContextConfi­guration), kedy pri vytváraní Bean carService sa injektuje CarDao.

S constructor Inject zaobchádzajte opatrne, môžete sa dostať do problémov s cyklickými závislosťami medzi Beana.


 

 

Článok pre vás napísal Petr Kunčar
Avatar
Ako sa ti páči článok?
Ešte nikto nehodnotil, buď prvý!
Nejlepší práce je taková, která vás baví. Nejlepší manželka je taková, co vás chápe. Nejlepší rodina je taková, co vás podporuje. Nejlepší relax je v přírodě. Nejlepší, co pro svět můžeš udělat, je řešit problémy rychle a elegantně.
Všetky články v sekcii
Java - Pre pokročilých
Aktivity (1)

 

 

Komentáre

Avatar
David Čápka
Tým ITnetwork
Avatar
David Čápka:12.9.2016 9:31

Dnes samé kvalitní články, super! Zrovna včera jsme dělali něco kolem našeho DI kontejneru systému ITnetwork a řešili jsme jak přidat dynamicky servisu za běhu aplikace, k čemuž je potřeba samozřejmě instance kontejneru. Také mě napadlo nechat si ApplicationContext normálně injectnout jako službu, ale přišlo mi to už moc divoké :D Jak vidím, tak se to někde používá.

Odpovedať
12.9.2016 9:31
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Avatar
Petr Beneš
Člen
Avatar
Petr Beneš:13.9.2016 18:16

Super článek. Škoda, že tu nebyl dřív. Se Springem jsem se docela ze začátku natrápil. Jen pro ujištění - za beanu lze považovat vše, co žije uvnitř kontejneru. V tom případě to platí mimo jiné pro classy s anotací @Configuration, @Component, @Controller, @Service, @Repository i objekty vrácené metodou s @Bean. Je to tak?

 
Odpovedať
13.9.2016 18:16
Tento výukový obsah pomáhajú rozvíjať nasledujúce firmy, ktoré možno hľadajú práve teba!
Avatar
Petr Kunčar
Redaktor
Avatar
Odpovedá na Petr Beneš
Petr Kunčar:14.9.2016 21:37

U @Configuration jsem si nebyl jisty, tak jsem se mrkl do implemntace a obsahuje @Component takze ano.

 
Odpovedať
14.9.2016 21:37
Avatar
Petr Beneš
Člen
Avatar
Odpovedá na Petr Kunčar
Petr Beneš:14.9.2016 23:23

Super, díky. Taky dobrý hint. Ověřit si to takhle mě nenapadlo. Upřímně jsem začal slovo bean nesnášet. Využívá se v tolika souvislostech, že mě to strašně mátlo Java Bean, EJB, CDI bean, @Bean, beans.xml, bean obecně, ManagedBean, Spring Bean...

 
Odpovedať
14.9.2016 23:23
Robíme čo je v našich silách, aby bola tunajšia diskusia čo najkvalitnejšia. Preto do nej tiež môžu prispievať len registrovaní členovia. Pre zapojenie sa do diskusie sa zaloguj. Ak ešte nemáš účet, zaregistruj sa, je to zadarmo.

Zobrazené 4 správy z 4.