[Java] Spring Boot DI / IoC (제어의 역전)

Onam Kwon·2022년 11월 1일
0

Java

목록 보기
5/7

Spring Boot DI / IoC

  • 자바의 웹프레임워크 Spring Boot를 공부하다 보면 DI/IoC라는 개념이 있다.
    • DIIoC패턴의 구현 방법중 하나로 주입이라는 것은 미리 준비된 객체를 넣어주는것을 의미함.
    • 새로운 객체가 필요할 때 마다 new를 이용해 생성하는게 아닌 bean(클래스)으로 미리 등록되어져 있는 클래스를 이용함.
      • bean은 스프링의 컨테이너에 의해 관리됨.
  • DI(Dependency Injection): 의존성 주입 이라는 뜻을 가지고 있다.
  • IoC(Inversion of Control): 제어의 역전으로 해석 가능.

Bean

  • Bean이란 스프링 컨테이너에 의해 관리당하는 자바 객체를 칭한다.
    • new연산자로 특정 객체를 생성했을때 그 객체는 빈으로 칭하지 않는다.
  • 스프링 부트를 접하기 전엔 각 객체들이 프로그램의 흐름을 결정하며 객체 또한 직접 생성 및 조작했다.
    • 사용자에 의해 모든 작업이 이루어짐.
    • A객체에서 B객체의 메소드를 사용하려면 B객체를 A객체 내부에서 생성 후 호출함.
  • IoC가 적용되면 객체의 라이프 사이클을 다른 관리 위임 주체에게 맡김.
    • 즉, 사용자의 제어권을 다른 주체에게 넘기는 것을 Inversion of Control(제어의 역전)라고 부른다.
  • 기존까지는 class를 생성 후 new연산자를 사용해 원하는 객체를 직접 생성했지만 스프링 부트에서는 스프링에 의해 관리 당하는 자바 객체를 사용한다. 이를 Bean이라고 한다.

DI / IoC

  • 스프링 컨테이너에 미리 등록해놓은 bean 하나하나의 객체들은 보통 의존적인 객체를 등록함. 이때, A객체가 B객체 없이 동작 불가능한 상황을 의존성있다, 의존관계이다, 두 클래스가 의존관계를 가지고있다 라고 표현한다.
  • 스프링의 많은 모듈, 라이브러리들은 DI를 기반으로 만들어져 있으며, 기본적으로 다 등록해 둔 후 꺼내쓰는 라는 개념.
  • 아래 사진은 새로운 스프링 부트 프로젝트를 생성 후 빈(클래스)와 메인 클래스 하나를 생성한 프로젝트 구조이다.

  • 위와 같이 세개의 자바 클래스 Hotel Restaurant Chef를 만든다.
    • Battery Device는 무시.
package practice.beans;

public class Hotel {
    private Restaurant restaurant;
    public void reserveRestaurant() {
        System.out.println("Booking ");
        restaurant.orderMenu();
    }
}
package practice.beans;

public class Restaurant {
    private Chef chef;
    public void orderMenu() {
        System.out.println("Ordering a menu...");
        chef.cook();
    }
}
package practice.beans;

public class Chef {
    public Chef() {
        System.out.println("Chef has been hired!");
    }
    public void cook() {
        System.out.println("Chef is cooking now...");
    }
}
  • 추가로 아래의 Main 클래스를 통해 브라우저의 / 경로로 왔을 때 콘솔 화면에 결과값이 실행되게 했다.
package practice.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import practice.beans.Chef;
import practice.beans.Hotel;
import practice.beans.Restaurant;

import static org.springframework.web.bind.annotation.RequestMethod.GET;

@Controller
public class Main {
    @RequestMapping(value={"/"}, method=GET)
    public String showMain() {
        Hotel hotel = new Hotel();
        hotel.reserveRestaurant();
        return "home";
    }
}

🔽 결과 🔽

  • 실행하다 중간에 에러가 났다.
2022-11-01 18:28:33.666  INFO 56999 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-11-01 18:28:33.667  INFO 56999 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
Booking 
2022-11-01 18:28:33.693 ERROR 56999 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke "practice.beans.Restaurant.orderMenu()" because "this.restaurant" is null] with root cause

java.lang.NullPointerException: Cannot invoke "practice.beans.Restaurant.orderMenu()" because "this.restaurant" is null
  • 위의 코드상 호텔은 식당 없이 운영이 되지 않으며 식당은 요리사 없이 운영되지 않는다.
  • HotelRestaurant의 객체 restaurant가 있어야 하며, RestaurantChef의 객체 chef가 있어야 한다.
    • reserveRestaurant()메소드 내부에서 restaurant객체를 참조하고 있음.
      • HotelRestaurant에 의존적임.
    • orderMenu()메소드 내부에서 chef객체를 참조하고 있음.
      • RestaurantChef에 의존적임.
  • 따라서 기존 코드를 수정해 의존성을 아래와 같이 부여해준다.

🔽 Hotel.java 🔽

package practice.beans;

public class Hotel {
    private Restaurant restaurant;
    public Hotel(Restaurant restaurant) {
        System.out.println("Hotel has been built!");
        this.restaurant = restaurant;
    }
    public void reserveRestaurant() {
        System.out.println("Reserving a restaurant...");
        restaurant.orderMenu();
    }
}

🔽 Restaurant.java 🔽

package practice.beans;

public class Restaurant {
    private Chef chef;
    public Restaurant(Chef chef) {
        System.out.println("Restaurant has been built!");
        this.chef = chef;
    }
    public void orderMenu() {
        System.out.println("Ordering a menu...");
        chef.cook();
    }
}

🔽 Chef.java 🔽

package practice.beans;

public class Chef {
    public Chef() {
        System.out.println("Chef has been hired!");
    }
    public void cook() {
        System.out.println("Chef is cooking now...");
    }
}

🔽 Main.java 🔽

package practice.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import practice.beans.Chef;
import practice.beans.Hotel;
import practice.beans.Restaurant;

import static org.springframework.web.bind.annotation.RequestMethod.GET;

@Controller
public class Main {
    @RequestMapping(value={"/"}, method=GET)
    public String showMain() {
        Chef chef = new Chef();
        Restaurant restaurant = new Restaurant(chef);
        Hotel hotel = new Hotel(restaurant);
        hotel.reserveRestaurant();
        return "home";
    }
}
  • 코드를 수정한 다음 적용하고 브라우저를 새로고침 하면 아래와 같은 결과를 얻을 수 있다.
2022-11-01 18:45:04.151  INFO 57153 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
Chef has been hired!
Restaurant has been built!
Hotel has been built!
Booking 
Ordering a menu...
Chef is cooking now...

@Component / @Autowired

  • 정상적으로 작동되지만 이 방법은 수동으로 개발자가 직접 의존성을 주입해줬다.
  • 스프링 부트에서 IoC를 하기 위한 여러가지 방법이 존재하지만 그중 한가지인 @Component 어노테이션을 사용하러 수 있다.
  • 빈으로 사용할 클래스 앞부분에 @Component만 붙혀주고 사용할땐 @Autowired 어노테이션으로 가져올 수 있다.

🔽 Hotel.java 🔽

package practice.beans;

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

@Component
public class Hotel {
    @Autowired
    private Restaurant restaurant;
    public void reserveRestaurant() {
        System.out.println("Reserving a restaurant...");
        restaurant.orderMenu();
    }
}

🔽 Restaurant.java 🔽

package practice.beans;

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

@Component
public class Restaurant {
    @Autowired
    private Chef chef;
    public void orderMenu() {
        System.out.println("Ordering a menu...");
        chef.cook();
    }
}

🔽 Chef.java 🔽

package practice.beans;

import org.springframework.stereotype.Component;

@Component
public class Chef {
    public Chef() {
        System.out.println("Chef has been hired!");
    }
    public void cook() {
        System.out.println("Chef is cooking now...");
    }
}

🔽 Main.java 🔽

package practice.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import practice.beans.Hotel;

import static org.springframework.web.bind.annotation.RequestMethod.GET;

@Controller
public class Main {
    @Autowired
    private Hotel hotel;
    @RequestMapping(value={"/"}, method=GET)
    public String showMain() {
        System.out.println("show main function has been called.");
        hotel.reserveRestaurant();
        return "home";
    }
}
  • 아래와 같은 결과를 얻을 수 있다.
2022-11-01 20:58:20.728  INFO 57525 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
show main function has been called.
Booking 
Ordering a menu...
Chef is cooking now...
  • 식당 객체에서 요리사 객체를 주입하지 않았으며, 호텔 객체에서도 식당 객체를 주입해 주지 않았지만 정상적으로 작동한다.
  • 스프링 빈의 의존성 주입을 이용하면 이와같이 직접적으로 객체를 주입해 주지 않아도 정상적으로 작동한다.

References

profile
권오남 / Onam Kwon

0개의 댓글