Zarábaj až 6 000 € mesačne! Akreditované rekvalifikačné kurzy od 0 €. Viac informácií.

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

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.


 

Všetky články v sekcii
Java - Pre pokročilých
Článok pre vás napísal Petr Kunčar
Avatar
Užívateľské hodnotenie:
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ě.
Aktivity