Spring - IoC Kontejner

Java Pro pokročilé Spring - IoC Kontejner

Spring je velmi rozšířený Java framework, který obsahuje několik různých projektů. Dalo by se říci, že všechny projekty spojuje jeden společný kontejner.

Inversion of Control

Spring kontejner využívá návrhový vzor Inversion of Control (IoC). Ten uvolňuje pevné vazby mezi objekty. Pevná vazba znamená, že třída si sama inicializuje své vlastnosti (jiné třídy, se kterými má vztah) a nedostane je z venčí.

Pevným vazbám se snažíme vyhýbat!

public interface CarDao {}

public class CarDaoImpl implements CarDao {}

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

Proč? Vaše architektura je příliš úzce svázaná (podobně jako u dědičnosti) a málo flexibilní. Změna kódu může být velice obtížná. Třída bez pevných vazeb se lépe testuje.

Jak? Princip IoC přesouvá odpovědnost za vznik vazeb na někoho jiného. V našem případě je přesunut z programátora na framework.

Dependency injection (DI)

Tento návrhový vzor souvisí přímo s IoC. Jedná se o mechanismus, kdy je do naší třídy vložena (injektována) instance jiné třídy. O tuto injekci se stará sám Framework podle konfigurace. Pro přehlednost a větší flexibilitu je dobré mít oddělenu konfiguraci od implementace. Existují tři typy injekce:

Property inject

Framework si sám najde danou property pomocí reflexe.

@Autowired
private CarDao carDao;

Constructor inject

Při vytváření pošle přes konstruktor instance potřebných component.

private CarDao carDao;

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

Setter inject

Při vytváření je nahrána instance pomocí setter-u.

private CarDao carDao;

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

Spring

Když již známe základní pojmy, pojďme se podívat, jak je využívá Spring. Zde je nutné znát dva pojmy:

Bean

Objekt, který vykonává nějakou funkčnost (např. přidává data do databáze, vyhledává...). Beany žijí v kontejneru po celý běh aplikace. Lze s ním pracovat v celé aplikaci. Existují dva typy bean (scope).

  • Singleton objekt je v celé aplikaci jen jednou. Pokaždé, pokud si řekneme o daný objekt aplikačnímu kontextu, dostaneme stejnou instanci.
  • Prototype je podobný jako singleton. Rozdíl je v tom, že pokud si řekneme o daný objekt aplikačnímu kontextu, dostaneme vždy novou instanci.

Kontejner

V kontejneru, neboli aplikačním kontextu, žijí objekty (BEAN), které tvoří funkční jádro vaší aplikace. Kontejner se zavádí při startu aplikace a reprezentuje ho třída ApplicationCon­text. V celé aplikaci je jen jeden a dá se injektovat odkudkoli.

Konfigurace aplikačního kontextu

Konfigurovat kontext můžeme pomocí XML souboru nebo pomocí Java class. Obě konfigurace si funkčně odpovídají. Preferovaná cesta je Java class.

Java konfigurace

Je realizována obyčejnou Java třídou, ve které jsou použity anotace pro tvorbu aplikačního kontextu. Je dobré, si pro konfigurační třídy udělat speciální package (configuration).

  • @Configuration vytvoří z dané třídy konfigurační třídu
  • @Import spojí dvě konfigurace (naimportuje jinou konfiguraci)
  • @Bean vytvoří beanu; typ je návratová hodnota a název je název metody (pokud se nepoužije name). Lze i změnit defaultní singleton scope (scope=DefaultSco­pes.PROTOTYPE)
  • @Autowired injektuje instanci jiné beany
  • @ComponentScan - proskenuje zadané package. Pokud narazí na speciální anotace (@Controller: prezentační vrstva; @Service: aplikační vrstva; @Repository: datová vrstva) vytvoří z daných tříd beany. Užitečná věc pro rychlou 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 konfigurace

Je reprezentována XML souborem. Konfigurace se musí nacházet v Resources a být na classpath.

  • <bean id="..." class="..."> vytvoří beanu
  • <import resource="..."/> import jiné konfigurace
  • <context:component-scan base-package="..." /> skenování package
Použití kontejneru

Pro práci s aplikačním kontextem slouží beana ApplicationContext. Tato bean má metodu getBean(), pomocí níž získáte jakoukoli beanu z kontejneru.

@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;
    }
}

Příklad je výtažek kódu, kde je využit návrhový vzor Factory. Podle změny (change) se rozhoduje kterou strategii má factory vytvořit (vytáhnout z aplikačního kontextu). V našem případě se jedná o NameStrategy.

Testování s mockito

Pokud neznáte mockito podívejte se na tento článek. Z článku se dovíte, že můžete injektované beany namockovat (@Mock) a sledovat, jestli byly v testu použity.

Je dobré otestovat také, že se vám správně sestaví aplikační kontext (inicializuje se kontejner). Zde lze využít metoda ApplicationCon­text.getBean();

Rady

Je čitelnější a flexibilnější pokud oddělíte konfiguraci aplikačního kontextu od implementace jednotlivých tříd.

Výrazná výhoda je v tom, že pokud budete chtít vyměnit framework (např. Spring za java EE), není to tak bolestivé. Stačí zahodit starou konfiguraci a vytvořit novou.

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);
        }
}

Jak je patrné z příkladu, třída CarServiceImpl využívá CarDao. Neobsahuje ovšem žádnou konfiguraci, žádné anotace @Autowired) a ani pevnou vazbu.

Konfigurace je prováděna v konfigurační třídě (ContextConfi­guration), kdy při vytváření beany carService se injektuje CarDao.

S constructor inject zacházejte opatrně, můžete se dostat do problémů s cyklickými závislostmi mezi beanami.


 

  Aktivity (2)

Článek pro vás napsal Petr Kunčar
Avatar
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ě.

Jak se ti líbí článek?
Celkem (1 hlasů) :
55555


 


Miniatura
Předchozí článek
Kolekce v Javě pro pokročilé
Miniatura
Všechny články v sekci
Java - Pro pokročilé

 

 

Komentáře

Avatar
David Čápka
Tým ITnetwork
Avatar
David Čápka:

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á.

Odpovědět 12. září 9:31
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
Petr Beneš
Člen
Avatar
Petr Beneš:

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?

 
Odpovědět 13. září 18:16
Avatar
Petr Kunčar
Redaktor
Avatar
Odpovídá na Petr Beneš
Petr Kunčar:

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

 
Odpovědět 14. září 21:37
Avatar
Petr Beneš
Člen
Avatar
Odpovídá na Petr Kunčar
Petr Beneš:

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...

 
Odpovědět 14. září 23:23
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 4 zpráv z 4.