이전 포스팅에서 AOP에 대해 알아보았다면 이번 시간에는 IoC/DI에 대해 알아보자.
IoC(Inversion of Control)란?
IoC란 '제어의 역전'이라고 한다. '제어의 역전'이라는 말은 상당히 어렵게 느껴지는 말이지만 이렇게 설명할 수 있다.
개발자가 제어하는 것이 아닌 스프링 컨테이너에게 제어권이 넘어간다.
그렇다면 여기서 말하는 스프링 컨테이너는 뭘까?
스프링에서는 오브젝트(빈)의 생성, 의존 관계 설정, 사용 등 작업을 스프링 컨테이너가 담당한다. 그래서 이를 IoC라고 부르고 스프링 컨테이너와 IoC 컨테이너는 같은 개념이라고 본다.
IoC를 적용하지 않은 방법과 IoC를 적용한 방법을 코드로 비교해보면 더 쉽게 이해할 수 있을 거라고 생각한다.
public class IoCEx1 {
A a ;
public IoCEx1(){
this.a = new A();
}
}
class A{
}
IoC를 적용하지 않으면 개발자가 직접 new 연산자를 사용해서 'a'라는 변수에 인스턴스를 할당해줘야 한다.
반면에 IoC를 적용한 방법을 어떨까?
public class IoCEx2 {
B b;
IoCEx2(B b){
this.b = b;
}
}
interface B{
}
@Component // 스프링 컨테이너가 관리할 수 있도록 하기 위한 어노테이션. 즉, Bean 등록을 해줌.
class BImpl implements B{
}
이전과 동일한 점은 어떤 클래스가 들어와야 하는지에 대한 정보가 있다는 것이다. 즉, IoCEx2 클래스는 B라는 타입의 객체가 필요하다는 것을 알고 있다는 것이다. 그렇다는 것은 B 타입이나 B 타입을 상속받은 클래스만 들어올 수 있다는 것이다.
반면, 다른 점은 IoC를 적용하니 개발자는 스프링 컨테이너에게 관리될 수 있도록 Bean 등록만 해주고 나머지 권한은 스프링 컨테이너에게 넘겨주었다. 그래서 스프링 컨테이너가 B 인터페이스를 상속한 BImpl의 객체를 생성하고 해당 객체를 할당시켜준 것이다.
개발자가 아닌 제3 자가 처리하도록 하는 것을 IoC(제어의 역전)이라고 한다.
DI(Dependency Injection)란?
위 관계를 보면 외부에서 두 객체 간의 관계를 결정해주고 있으며, 인터페이스를 사이에 두어 런타임 시에 관계를 주입하여 느슨한 결합도를 보여준다. 'B → A'라는 의존 관계가 성립하고 A는 B에 의존한다고 할 수 있다. B의 내용에 따라 A가 변할 수 있기 때문이다.
외부에서 의존 주입을 해주는 것을 DI(Dependency Injection)라고 한다. 우리말로는 '의존 관계 주입'이라고 한다.
의존 관계를 주입하는 방법을 3가지로 분류한다.
- 필드 주입
- 수정자 주입
- 생성자 주입
필드 주입
@Component
public class IoCEx2 {
@Autowired
B b;
}
interface B{
}
@Component
class BImpl implements B{
}
@Autowired 어노테이션을 사용한다. Bean 등록된 객체 중에 타입에 맞는 객체를 주입해주는 것이다. 가장 간단하기 때문에 사용하기 편하지만 지양해야 할 DI 방법이다.
장점
- 가장 간편하다.
단점
- 의존성이 눈에 안 뜨인다. → 다른 방법에 비해 한눈에 의존 관계를 파악하기 어렵다.
- DI 컨테이너와 결합도가 커지고, 테스트하기가 어렵다.
- 불변성을 보장할 수 없고 순환 참조가 발생할 수 있다.
수정자 주입
@Component
public class IoCEx2 {
private B b;
public void setB(B b){
this.b = b;
}
}
interface B{
}
@Component
class BImpl implements B{
}
장점
- 선택적인 의존성을 할 수 있다는 것이다.
단점
- 선택적인 의존성을 할 수 있다는 말은 곧 IoCEx2에 모든 구현체를 주입받지 않아도 B 객체를 생성하고 객체의 메서드를 호출할 수 있다는 것이다. 그래서 주입받지 않은 구현체를 사용하는 메서드에서 NullPointException이 발생할 수 있다.
- 순환 참조의 문제가 발생한다.
생성자 주입
@Component
public class IoCEx2 {
private B b;
IoCEx2(B b){
this.b = b;
}
}
interface B{
}
@Component
class BImpl implements B{
}
DI 방법 중에서 가장 권장되는 주입 방식이다.
장점
- 모든 의존 관계를 주입해야 객체 생성이 가능하므로 NullPointException이 발생하지 않는다.
- 순환 참조를 컴파일 과정에서 찾아낼 수 있다.
- final 키워드를 사용하여 불변성을 보장할 수 있다.
▽ 도움을 주신 분들
'Spring > Spring' 카테고리의 다른 글
[Spring] Spring Boot에서 MockMvc 사용해보자! (0) | 2022.07.15 |
---|---|
[Spring] Spring Triangle? 그게 뭔데 - PSA (0) | 2022.07.05 |
[Spring] Spring Triangle? 그게 뭔데 - AOP (0) | 2022.06.29 |
[Spring] 인터셉터(Interceptor)에 대해 알아보자! (0) | 2022.06.28 |
[Spring] Filter에 대해 알아보자 ! (0) | 2022.06.24 |