Dzisiejszy wpis będzie dotyczyć kolejnego konceptu zaczerpniętego z bloga SpringDeveloper.pl. Mianowicie weźmiemy na tapet kontener IoC (Inversion of Control) Springa, inaczej nazywanego kontenerem zależności. Jest to jedna z jego podstawowych funkcjonalności. Można powiedzieć, że to dzięki niej Spring zyskał taką popularność. Jej fenomen polega na tym, że programiści zostali zwolnieni z odpowiedzialności za instancjonowanie obiektów. To magia Springa ma za zadanie stworzyć te obiekty i połączyć je ze sobą. Nie zwlekając, przejdźmy do sedna i odczarujmy tą jakże ciekawą podwalinę Springa!

Czym jest kontener IoC?

Jeśli nie spotkałeś/aś się wcześniej z zagadnieniem Inversion of Control to odsyłam Cię do mojego wcześniejszego artykułu na ten temat. Przedstawiłem w nim działanie tego mechanizmu na bardzo prostym przykładzie. Natomiast jeśli już zjadłeś/aś zęby na odwracaniu zależności to pora pójść o krok dalej. Wyjaśnijmy sobie czym jest kontener zależności.

Kontener IoC jest abstrakcyjnym bytem, który zarządza cyklem życia obiektów będących pod jego kontrolą. Co to właściwie oznacza? Jest on odpowiedzialny za ich stworzenie, gospodarowanie nimi w trakcie działania programu oraz przekazywanie do innych elementów aplikacji (poprzez wykorzystanie Dependency Injection). W ten sposób programista ma za zadanie tylko (i aż) dostarczyć schematy do kreowania obiektów oraz napisać konfiguracje zrozumiałą dla Springa, aby połączyć ze sobą te obiekty.

Schemat konfiguracji kontenera zależności, aby uzyskać gotową aplikację Schemat konfiguracji kontenera zależności, aby uzyskać gotową aplikację

Pisząc sformułowanie “schematy do kreowania obiektów” miałem na myśli oczywiście klasy. To w nich definiujemy niezbędne zależności, które pozwalają ich instancjom prawidłowo funkcjonować. Dzięki argumentom konstruktora czy też parametrom metody fabrykującej możemy te zależności wypełnić. Istnieje także możliwość przekazywania zależności do już stworzonego obiektu np. poprzez settery. Zagadnieniem konfiguracji zajmiemy się natomiast już za chwilę. Gdy te dwa aspekty zostaną spełnione to Spring, wykorzystując kontener zależności, całą resztą zajmie się za nas.

Jak to wygląda od strony kodu?

Spring reprezentuje koncept kontenera IoC w kodzie poprzez interfejs ApplicationContext. Dzięki niemu, a dokładniej jego implementacjom, instancjonowane, konfigurowane i dostarczane są beany w aplikacji. No właśnie, beany. To nic innego jak obiekty (POJO) stworzone i zarządzane przez kontener IoC. Jednak aby framework to wszystko za nas posklejał potrzebuje mieć na to przepis tzn. konfigurację. Jej stworzenie spoczywa już na nas, programistach.

Dokonać możemy tego na kilka sposobów. Najbardziej podstawowym oraz coraz rzadziej stosowanym są pliki konfiguracyjne XML. To rozwiązanie ma ciekawą zaletę w postaci braku zależności w kodzie produkcyjnym do Springa. Jednak ciężkość utrzymania i łatwość w dokonywaniu pomyłek powodują, że w nowych projektach z XMLa się po prostu nie korzysta. Alternatywnym rozwiązaniem są adnotacje dostarczone przez framework takie jak @Component, @Service itp., które umieszcza się nad klasą. Można również tworzyć tzw. klasy konfiguracyjne definiujące beany. Jednak o tych dwóch sposobach rozpiszę się bardziej w jednym z kolejnych wpisów.

Na ten moment chciałbym Ci jeszcze zaprezentować ile beanów dostajemy ad hoc od Springa (a dokładniej Spring Boota, o którym porozmawiamy sobie później) w nowym projekcie.

Weryfikacja kontenera Springa w praktyce

W tym celu skorzystamy z metody SpringApplication.run. Wynikiem jej wykonania jest zwrócenie instancji implementującej interfejs ConfigurableApplicationContext. On natomiast rozszerza wcześniej wspominany interfejs ApplicationContext. Jeśli na zmiennej o takim typie użyjemy metody getBeanDefinitionNames (z interfejsu ListableBeanFactory) to otrzymamy nazwy wszystkich istniejących beanów. Poniżej znajduje się kod, który wykona postawione przez nas zadanie.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package pl.springdeveloper.containerioc;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class ContainerIocApplication {

  private static final Logger LOGGER = LoggerFactory.getLogger(ContainerIocApplication.class);

  public static void main(String[] args) {
    ApplicationContext context = SpringApplication.run(ContainerIocApplication.class, args);

    String[] beanDefinitionNames = context.getBeanDefinitionNames();

    LOGGER.info("Number of beans: {}", beanDefinitionNames.length);
    LOGGER.info("List of beans: {}", String.join(", ", beanDefinitionNames));
  }
}

Po uruchomieniu w logach aplikacji dostaniemy następujący wynik działania tego kawałka kodu.

1
2
2022-07-29 13:08:22.643  INFO 23248 --- [           main] p.s.c.ContainerIocApplication            : Number of beans: 46
2022-07-29 13:08:22.644  INFO 23248 --- [           main] p.s.c.ContainerIocApplication            : List of beans: org.springframework.context.annotation.internalConfigurationAnnotationProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor, org.springframework.context.event.internalEventListenerProcessor, org.springframework.context.event.internalEventListenerFactory, containerIocApplication, org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory, testBean, org.springframework.boot.autoconfigure.AutoConfigurationPackages, org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration, propertySourcesPlaceholderConfigurer, org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration, mbeanExporter, objectNamingStrategy, mbeanServer, org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor, org.springframework.boot.context.internalConfigurationPropertiesBinderFactory, org.springframework.boot.context.internalConfigurationPropertiesBinder, org.springframework.boot.context.properties.BoundConfigurationProperties, org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar.methodValidationExcludeFilter, spring.jmx-org.springframework.boot.autoconfigure.jmx.JmxProperties, org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration, springApplicationAdminRegistrar, org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration, forceAutoProxyCreatorToUseClassProxying, org.springframework.boot.autoconfigure.aop.AopAutoConfiguration, org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration, applicationAvailability, org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration, org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration, lifecycleProcessor, spring.lifecycle-org.springframework.boot.autoconfigure.context.LifecycleProperties, org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration, spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties, org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration, spring.sql.init-org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties, org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor, org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration, taskExecutorBuilder, applicationTaskExecutor, spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties, org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration, scheduledBeanLazyInitializationExcludeFilter, taskSchedulerBuilder, spring.task.scheduling-org.springframework.boot.autoconfigure.task.TaskSchedulingProperties, org.springframework.aop.config.internalAutoProxyCreator

Prawda, że ta liczba robi wrażenie?

Podsumowanie

Mam nadzieję, że ten wpis zarysował Ci czym dokładnie jest kontener zależności i do czego on służy. W następnym artykule zanurzymy się w meandry konfigurowania obiektów (beanów), którymi zarządza za nas Spring. Tak jak wspomniałem, skupimy się wyłacznie na kodzie napisanym w Javie, a nie w XMLu. Już nie mogę się doczekać, a Ty? Oczywiście zachęcam Cię do zadawania pytań jeśli jakieś masz co do tego tematu. Najlepiej napisz je w sekcji komentarzy. Wtedy inne osoby będą mogły też z nich skorzystać.