Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Spring - IoC Kontejner

V předchozí lekci, REST API v Java Spring Boot - Detail uživatele a zabezpečení, jsme dokončili přihlášení uživatele k našemu API a nadefinovali si přístupová pravidla k jednotlivým akcím.

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.


 

Předchozí článek
REST API v Java Spring Boot - Detail uživatele a zabezpečení
Všechny články v sekci
REST API ve Spring Boot - Filmová databáze
Článek pro vás napsal Petr Kunčar
Avatar
Uživatelské hodnocení:
1 hlasů
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