MS Lesson6: IoC, DI, Spring Context, ApplicationContext

 1. IoC - Inversion of Control

Ən əsas ideya budur:
  • Əvvəl (klassik Java): sən obyektləri özün yaradırsan (new Service()new Repo()), kim kimdən asılıdır hamısını özün bağlayırsan.
  • IoC-də: bu nəzarəti container-ə verirsən (Spring). O obyektləri özü yaradır və bağlayır.
Yəni nəzarət istiqaməti çevrilir:
  • əvvəl: sən idarə edirdin
  • indi: framework idarə edir
Buna görə adı “Inversion of Control”.


2. DI - Dependency Injection

DI, IoC-ni praktikada həyata keçirmə üsuludur.
Sənin class-ın başqa class-a ehtiyac duyursa, o dependency-ni içəridə new etmirsən.
Kənardan verilir (inject olunur).
Məsələn:
  • TestController-a StudentIdGeneratorService lazımdır.
  • Constructor ilə veririk:
  • new TestController(service) (unit testdə)
  • və ya Spring onu avtomatik verir (runtime-da)
DI növləri:
  • constructor injection (ən yaxşı)
  • setter injection
  • field injection


3. Spring Context

Spring Context = Spring-in işləyən IoC container-ıdır.
Nə edir?
  • @Component, @Service@Repository@Controller, @Configuration tapır
  • bunlardan bean yaradır
  • DI edir
  • bean lifecycle idarə edir (initdestroy)
  • config/property binding edir
Sadə dillə:
“obyekt fabriki + wiring mərkəzi
Sən @Autowired yazırsansa, onu dolduran məhz context-dir.


4. ApplicationContext

ApplicationContext, Spring context-in konkret interfeysidir.
Praktik olaraq “Spring Context” deyəndə çox vaxt bunu nəzərdə tuturuq.
Texniki olaraq:
  • BeanFactory daha əsas/sadə container API-dir
  • ApplicationContext onun üstündə daha geniş funksionallıq verir:
  • event publishing
  • internationalization
  • AOP/auto-config ilə daha tam inteqrasiya
  • environment/profiles və s.
Spring Boot app başlayanda faktiki bir ApplicationContext qalxır.


  • IoC = prinsip (idarəni framework-ə vermək)
  • DI = mexanizm (dependency-ni inject etmək)
  • ApplicationContext (Spring Context) = bunu edən container


  • Spring Context = ümumi danışıq adıdır.
  • ApplicationContext = bunun real, texniki adıdır (Spring-də ən çox istifadə edilən container interfeysi).
Yəni praktikada çox vaxt eyni şeyi deyirlər.
Sadə analoq:
  • “maşın” deyəndə ümumi addır
  • “Toyota Corolla” deyəndə konkret modeldir
Burda:
  • “Spring Context” = maşın
  • “ApplicationContext” = konkret model (ən çox işlənən)




Applicationda olan butun bean-lari gosterir

@Bean
ApplicationRunner runner(ApplicationContext ctx) {
return args -> {
String[] names = ctx.getBeanDefinitionNames();
Arrays.sort(names);
for (String name : names) {
System.out.println(name);
}
};
}



SpringApplication.run(...) işləyir

Bu metod 3 əsas iş görür:

  • Spring Boot konfiqurasiyasını oxuyur (@SpringBootApplicationapplication.yaml, profillər və s.)
  • Hansı tip application olduğuna qərar verir (web, reactive, cli və s.)
  • ApplicationContext obyektini YARADIR

Bu məqamdan sonra artıq “Spring Container” (yəni ApplicationContext) var.


ApplicationContext yaradılır

Məsələn, sənin proyektində bu tip bir class olur:

  • AnnotationConfigServletWebServerApplicationContext

Bu obyekt:

  • Daxilində bir BeanFactory saxlayır (bean-ları yaradan mexanizm)
  • Bütün application-ın “beyni” olur:
    • Hansı bean var?
    • Hansının asılılıqları nədir?
    • Həyat dövrləri nədir? (singleton, prototype və s.)

Cümlə ilə:
“Spring işə düşəndə ilk iş kimi bir dənə ApplicationContext obyekti yaradır. Bu obyekt sonra bütün bean-ları tapıb yaradan və idarə edən konteynerdir.”


Component scan: Bean-ların tapılması

ApplicationContext yaradıldıqdan sonra Spring belə edir:

  • @SpringBootApplication olan sinfin paketindən başlayır
  • Bu paket və alt paketlərdə:
    • @Component
    • @Service
    • @Repository
    • @Controller / @RestController
    • @Configuration
    • @ControllerAdvice və s.

olan bütün sinifləri tapır.

Hər tapdığı sinif üçün bean definition yaradır:

  • Class adı
  • Scope (singleton, prototype və s.)
  • Lazım olan dependency-lər (@Autowired, constructor parametrləri)
  • Proxy lazımdırmı? (transaction, AOP və s.)

Bu mərhələdə hələ obyektlər tam yaradılmır, sadəcə “planı çəkilir”.


Bean-ların yaradılması (instantiate + dependency injection)

Sonra ApplicationContext aşağıdakıları edir:

  1. Singleton bean-ları (default scope) start zamanı yaradır:

    • StudentController
    • StudentServiceImpl
    • StudentRepository
    • CourseRepository
    • DataSource (HikariCP)
    • EntityManagerFactory
    • TransactionManager
    • və s.
  2. Hər bean üçün:

    • Constructor tapılır
    • Lazım olan dependency-lər müəyyən olunur
      • Məsələn StudentServiceImpl üçün StudentRepository
    • Əvvəl dependency yaradılır, sonra həmin dependency-lə birlikdə bu bean yaradılır
    • Field / setter injection varsa, onlar da set olunur

Bu mərhələyə “instantiate + dependency injection” kimi baxmaq olar.


Xüsusi bean-lar: DataSource, Hikari, EntityManagerFactory

Spring Boot starter-lar sayəsində avtomatik bunları da yaradır:

  • DataSource → HikariCP connection pool
  • EntityManagerFactory → JPA/Hibernate üçün “zavod”
  • PlatformTransactionManager → @Transactional üçün transaction idarəçisi
  • JPA Repositories (StudentRepositoryCourseRepository) üçün proxy-lər

Bu bean-lar da ApplicationContext-in içindədir, sadəcə avtoconfiguration tərəfindən yaradılır (sən yazmamısan, Spring Boot sənin yerinə yazıb).


 Bean life-cycle callback-lər (PostConstruct, Initializers)

Bu mərhələdə Spring:

  • @PostConstruct annotasiyalı metodları çağırır
  • InitializingBean implement edənlər üçün afterPropertiesSet() çağırır
  • ApplicationRunner / CommandLineRunner bean-larını işə salır


Web server-in işə düşməsi (Tomcat, Jetty və s.)

Əgər bu web tətbiqdirsə (səndə Spring MVC var):

  • Spring Boot embedded Tomcat bean-ını yaradır (TomcatWebServer)
  • Tomcat 8080 portunda listen etməyə başlayır
  • Spring MVC DispatcherServlet Tomcat-ə qeydiyyatdan keçir
  • @RestController endpoint-lər route kimi qeyd olunur









SpringContext vs ApplicationContext

Spring Context vs Application Context

Spring Context — ümumi bir anlayışdır. Spring-in IoC (Inversion of Control) konteynerini ifadə edir. Yəni bean-ləri yaradan, idarə edən və aralarındakı asılılıqları inject edən mexanizmdir.

ApplicationContext isə bu konteynerin konkret bir interface-idir. BeanFactory-nin genişləndirilmiş versiyasıdır və əlavə xüsusiyyətlər təqdim edir:

  • Event publication
  • Internationalization (i18n)
  • Resource loading
  • AOP dəstəyi


Spring Boot-da praktiki olaraq:

Spring Boot işə düşəndə avtomatik olaraq bir ApplicationContext yaradır. Hansı tip yaradılır, layihənin növündən asılıdır:

  • Web layihəsiAnnotationConfigServletWebServerApplicationContext
  • Reactive layihəAnnotationConfigReactiveWebServerApplicationContext
  • Adi layihəAnnotationConfigApplicationContext


@SpringBootApplication

public class MyApp {

    public static void main(String[] args) {

        // Bu ApplicationContext qaytarır

        ApplicationContext ctx = SpringApplication.run(MyApp.class, args);

        

        // Bean-ə əl ilə çatmaq

        MyService service = ctx.getBean(MyService.class);

    }

}




Bean Lifecycle





1️⃣ Container Started

Spring işə düşür, ApplicationContext yaradılır. Spring hansı bean-lərin olduğunu scan edir (@Component, @Service, @Repository və s.)


2️⃣ Bean Instantiated

Spring bean-in obyektini yaradır. Yəni new əməliyyatı baş verir.


ApplicationContext başlayanda bütün bean-ləri yaradıb öz içinə atır (saxlayır).

ApplicationContext əslində bir bean anbarı kimidir. Yaradır, saxlayır, lazım olanda verir.

ApplicationContext yalniz singleton beanlari icinde saxlayir:

package com.amazon.demoapp;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.context.WebApplicationContext;

@SpringBootApplication
@RequiredArgsConstructor
public class DemoAppApplication implements CommandLineRunner {

private final ApplicationContext applicationContext;

public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(DemoAppApplication.class, args);
System.out.println(run.getClass().getName());
}

@Override
public void run(String... args) throws Exception {
Object student1 = applicationContext.getBean("getStudent");
Object student2 = applicationContext.getBean("getStudent");

System.out.println(student1 == student2);
System.out.println(System.identityHashCode(student1));
System.out.println(System.identityHashCode(student2));
}

}



1) Singleton — düzgün istifadə

Niyə singleton yaxşıdır?
  • Heç bir field yoxdur (state yoxdur)
  • Hər istifadəçi eyni metodu çırır
  • 1 instance bütün app üçün kifayətdir


2) Singleton — SƏHV istifadə

 problem yaranır?
  • İstifadəçi A addItem("kitab") edir
  • İstifadəçi B addItem("qələm") edir
  • İstifadəçi A getItems() çağırır
  • Cavab: [kitab, qələm] — yəni B-nin məlumatı da görünür
Bütün thread-lər eyni instance-ı paylaşdığı üçün state qarışır.


3) Prototype — düzgün istifadə

İndi nə olur?
  • İstifadəçi A üçün ayrı instance yaranır
  • İstifadəçi B üçün ayrı instance yaranır
  • Hər birinin öz items listı var
  • Data qarışmır



3️⃣ Dependencies Injected

Bean-in ehtiyac duyduğu digər bean-lər inject edilir (@Autowired, constructor injection və s.)

@Service

public class MyService {

    

    @Autowired

    private MyRepository repository; // ← bu mərhələdə inject olunur

}



Circular Injection

package com.amazon.demoapp.conflict;

public interface Aservice {
}


package com.amazon.demoapp.conflict;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AserviceImpl implements Aservice{

@Autowired
private Bservice bservice;

}


package com.amazon.demoapp.conflict;

public interface Bservice {
}


package com.amazon.demoapp.conflict;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BserviceImpl implements Bservice{

@Autowired
private Aservice aservice;

}


Hell yollari:

1. @Lazy

  • Bean əvvəlcə yaradılmır, ilk istifadədə yaranır.
  • Circular dependency texniki olaraq aradan qalxır.
  • Amma real problem həll olunmur, sadəcə "gizlədilir".


@Lazy ilə problem

  • Bean ilk istifadədə yaranır.
  • Yəni startup-da problem yoxdur, amma runtime-da NullPointerException və ya gözlənilməz davranış ola bilər.
  • Debugging çətinləşir — problem startup-da deyil, istifadə anında çıxır.
  • Kod oxuyan developer "niyə @Lazy var?" sualını verəcək.


2. Setter injection

Constructor injection circular dependency-ni startup-da dərhal aşkar edir.
Setter injection ilə Spring bean-ı əvvəl yaradır, sonra inject edir.
Yəni texniki problem aradan qalxır — amma yenə ideal həll deyil.
@Service
public class AserviceImpl implements Aservice {
private Bservice bservice;

@Autowired
public void setBservice(Bservice bservice) {
this.bservice = bservice;
}
}


@Service
public class BserviceImpl implements Bservice {
private Aservice aservice;

@Autowired
public void setAservice(Aservice aservice) {
this.aservice = aservice;
}
}

Setter injection ilə problem

  • final istifadə edilə bilmir → immutability yoxdur.
  • Setter çağrılmamışsa field null qalır → NPE riski.
  • Test-də setter əl ilə çağrılmalıdır.
  • "Bu dependency vacibdirmi, optional-dırmı?" sualı açıq qalır.


3. Dizayni yeniden dushunmek (en yaxshi yol)

@Service
public class OrderService {
private final PaymentService paymentService;

public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}

public void placeOrder(Long orderId) {
// sifariş məntiqi...
paymentService.processPayment(orderId);
}

public void updateOrderStatus(Long orderId, String status) {
// status yenilə
}
}


@Service
public class PaymentService {
private final OrderService orderService;

public PaymentService(OrderService orderService) {
this.orderService = orderService;
}

public void processPayment(Long orderId) {
// ödəniş məntiqi...
orderService.updateOrderStatus(orderId, "PAID"); // circular!
}
}


Helli:

@Service
public class OrderPaymentCoordinator {

private final OrderService orderService;
private final PaymentService paymentService;

public OrderPaymentCoordinator(OrderService orderService,
PaymentService paymentService) {
this.orderService = orderService;
this.paymentService = paymentService;
}

public void placeOrderWithPayment(Long orderId) {
paymentService.processPayment(orderId);
orderService.updateOrderStatus(orderId, "PAID");
}
}


@Service
public class OrderService {

public void updateOrderStatus(Long orderId, String status) {
// yalnız öz məntiqi
}
}


@Service
public class PaymentService {

public void processPayment(Long orderId) {
// yalnız öz məntiqi
}
}



4️⃣ Custom init() method

Bean tamamilə hazır olduqdan sonra işə düşür. Məsələn DB connection yoxlamaq, cache doldurmaq və s.

@Component

public class MyService {


    @PostConstruct // ← init method

    public void init() {

        System.out.println("Bean hazırdır, işə başlayıram!");

    }

}


5️⃣ Custom Utility method

Bean artıq tam işlək vəziyyətdədir. Bütün iş məntiqi (business logic) burada icra olunur. Yəni application işlədiyi müddətdə bu mərhələdə qalır.


6️⃣ Custom destroy() method

Application bağlananda və ya bean silinəndə çağırılır. Resursları sərbəst buraxmaq üçün istifadə olunur.







Комментарии

Популярные сообщения из этого блога

Interview questions

Lesson1: JDK, JVM, JRE

Lesson_2: Operations in Java