- 자바의 웹프레임워크
Spring Boot
를 공부하다 보면DI
/IoC
라는 개념이 있다.
DI
는IoC
패턴의 구현 방법중 하나로 주입이라는 것은 미리 준비된 객체를 넣어주는것을 의미함.- 새로운 객체가 필요할 때 마다
new
를 이용해 생성하는게 아닌bean(클래스)
으로 미리 등록되어져 있는 클래스를 이용함.
bean
은 스프링의 컨테이너에 의해 관리됨.DI(Dependency Injection)
: 의존성 주입 이라는 뜻을 가지고 있다.IoC(Inversion of Control)
: 제어의 역전으로 해석 가능.
Bean
이란 스프링 컨테이너에 의해 관리당하는 자바 객체를 칭한다.
new
연산자로 특정 객체를 생성했을때 그 객체는 빈으로 칭하지 않는다.- 스프링 부트를 접하기 전엔 각 객체들이 프로그램의 흐름을 결정하며 객체 또한 직접 생성 및 조작했다.
- 사용자에 의해 모든 작업이 이루어짐.
- A객체에서 B객체의 메소드를 사용하려면 B객체를 A객체 내부에서 생성 후 호출함.
IoC
가 적용되면 객체의 라이프 사이클을 다른 관리 위임 주체에게 맡김.
- 즉, 사용자의 제어권을 다른 주체에게 넘기는 것을
Inversion of Control(제어의 역전)
라고 부른다.- 기존까지는
class
를 생성 후new
연산자를 사용해 원하는 객체를 직접 생성했지만 스프링 부트에서는 스프링에 의해 관리 당하는 자바 객체를 사용한다. 이를Bean
이라고 한다.
- 스프링 컨테이너에 미리 등록해놓은
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
- 위의 코드상 호텔은 식당 없이 운영이 되지 않으며 식당은 요리사 없이 운영되지 않는다.
Hotel
에Restaurant
의 객체restaurant
가 있어야 하며,Restaurant
에Chef
의 객체chef
가 있어야 한다.
reserveRestaurant()
메소드 내부에서restaurant
객체를 참조하고 있음.
Hotel
은Restaurant
에 의존적임.orderMenu()
메소드 내부에서chef
객체를 참조하고 있음.
Restaurant
는Chef
에 의존적임.
- 따라서 기존 코드를 수정해 의존성을 아래와 같이 부여해준다.
🔽 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...
- 정상적으로 작동되지만 이 방법은 수동으로 개발자가 직접 의존성을 주입해줬다.
- 스프링 부트에서 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...
- 식당 객체에서 요리사 객체를 주입하지 않았으며, 호텔 객체에서도 식당 객체를 주입해 주지 않았지만 정상적으로 작동한다.
- 스프링 빈의 의존성 주입을 이용하면 이와같이 직접적으로 객체를 주입해 주지 않아도 정상적으로 작동한다.