의존성, DI, DIP에 대한 개념을 모른다면 먼저 선행을 하길 바란다. ➡ 클릭
결론부터 말하면, 앞서 배운 DIP와 같은 목적이라고 할 수 있다. 하지만 DIP랑은 또 다른 개념이다.
IoC(Inversion of Control)은 "제어의 역전"이다.
제어: 어떠한 클래스 내부에서 다른 객체를 생성하고 이용할 때, 직접 필드를 '제어'한다.
제어의 역전: 객체를 클래스 내부에서 직접 생성하고 제어하는 것이 아니라, 미지의 외부로 그 제어권을 넘겨주는 것을 의미한다. 대표적으로 생성자 주입
방식은 IoC 원칙을 구현하기 위한 하나의 방식이다.
DIP는 상위 모듈
하위 모듈
을 추상화에 의존하게 하여, 상위 모듈
과 하위 모듈
간의 결속을 낮추기 위한 하나의 원칙이다.
정리하자면, IoC, DIP 모두 디자인 패턴과 같은 원칙이며, 한 클래스의 변경이 다른 클래스에 대해 미치는 영향이 최소가 되도록, 변경에 유연한 코드가 되도록 하는 것에 같은 목적을 둔다.
하지만 IoC, DIP 의 방식은 목적은 같지만, IoC는 제어권
이라는 관점, DIP는 의존성
이라는 관점에 대한 차이이다.
일반적인 프로그램을 만들 때는, main 함수로 부터 그 진행 흐름을 컨트롤 할 수 있다. 하지만 프레임워크를 사용할 때는, 프레임워크를 실행하는 작업만 수행하고, 내가 작성한 코드는 따로 호출하지 않는다.
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Spring Boot 에서는 main 함수에서 그냥 run()만 했을 뿐이고, 내가 Controller, Service,.. 등 내가 작성한 코드를 호출하는 부분은 없다.
내가 작성한 코드(객체)의 생성/초기화/실행/종료 등을 알아서 처리해주는 것이 바로 프레임워크의 컨테이너이다.
그리고 이 컨테이너는 프로그램의 실행 흐름이나 객체의 생명 주기를 개발자가 직접 제어하는 것이 아니라, 컨테이너로 제어권이 넘어가는 것을 앞서 설명한 제어의 역전(Inversion of Control)이라고 한다.
따라서 우리는 Spring Container을 IoC Container라고 부르고, IoC Container가 관리하는 객체들을 Bean이라고 부른다.
🧷Bean들을 담고 있는 IoC 컨테이너는 두 가지 중 하나를 사용한다.
- ApplicationContext 혹은 BeanFactory
- ApplicationContext는 BeanFactory를 상속받으므로 둘 다 같은 일을 하는 것임
앞서 말했듯이, 컨테이너는 Bean들의 의존성을 관리하고, 객체를 만들어 주며, Bean으로 등록을 해 주고, 이렇게 만들어진 것들을 관리한다.
따라서 개발자가 이 부분까지 신경쓰지 않고, 프레임워크의 기능만 구현하면 된다.
- 스프링에서의 의존성 주입은 반드시 Bean으로 등록된 객체들 끼리만 가능하다
- Bean은 디폴트로 싱글톤 생성이며, 원하는 경우 사용자 프로토타입으로 생성할 수도 있다.
Bean으로 등록하기 위한 다음 2가지 어노테이션이 있다.
@Component
- Class에 선언
@Configuration
은 실은 그 안에 @Component를 선언하고 있다.@Controller
,@Service
,@Repository
, .. 도@Configuration
과 마찬가지로, @Component를 선언하고 있다.//프로토 타입 Bean 선언 @Component @Scope("prototype") public class Proto {}
@Bean
- 메소드에 선언
- 외부 라이브러리를 빈으로 등록 가능
@Component
@Bean
의 차이는 간단하게 이 정도만 알고 있고, 자세한 내용은 따로 포스트 할 예정이다.
위에서도 말했듯이 컨테이너는 Bean 객체의 의존성 주입을 자동으로 관리한다.
다음과 같이 PayService라는 추상체(인터페이스)를 구현받는 NaverPayService
, KakaoPayService
등 2개 이상의 구현체가 존재한다고 할 때, 컨테이너는 어떤 구현체에 의존성을 자동 주입할 지 정하지 못해서 충돌이 일어난다.
충돌이라고 표현한 이유는 Bean들은 (key,val) 테이블 형태로 저장되기 때문이다.
이 문제 해결을 위한 다음 3가지 방법이 있다.
스프링은 메소드의 매개변수 명과 Bean의 이름이 일치하면, 해당 빈으로 자동으로 의존성 주입을 해준다.
인터페이스를 구현받는 여러 구현체 중 하나의 구현체만 의존성을 주입하고 싶을 경우, 해당 클래스에 @Primary
를 지정하면 된다.
클래스에 @Qualifier
를 주입하고, @Qualifier
에 주입한 구분자와 같은 것을 메소드의 매개변수에도 붙여주면 의존성이 주입된다.
타입 ➡ @Qualifier ➡ @Primary ➡ 변수 명
우선순위는
@Qualifier > @Primary > 변수 명
이다.