본문 바로가기

Language/Spring

의존성 주입(DI), 제어의 역전(IoC)

IoC(Inversion of Control)

  • 제어의 역전(IoC)이란 객체의 생성 및 생명주기 관리에 대한 책임이 개발자에서 프레임워크에게 넘어가는 것
  • Spring을 사용했을 때의 강력한 이점 중 하나Bean을 Spring Container에 등록을 해두고, 필요한 경우 등록된 Bean을 주입하여 자동으로 필요한 객체를 받을 수 있음
  • 개발자가 직접 객체 생성, 관리를 하지 않아도 되기 때문제어의 역전이라고 함

 

  • 기존 방식
public class A {
	private B b;
    
    pulbic A(){
    	this.b = new B();// 직접 객체를 생성하여 넣어줌
    }
}

 

  • IoC
public class A{
	private B b;
    
    public B(B b){
    	this.b = b; // 객체를 생성하지 않음
    }
}

DI(Dependency Injection)

  • 객체를 직접 생성하지 않고 외부(스프링 컨테이너)에서 받아 사용하는 방식
  • 의존성이란 하나의 객체(A)가 다른 객체(B)를 필요할 때, A는 B에 의존하고 있다고 표현
  • 의존성 주입이란 하나의 객체(A)가 필요로 하는 다른 객체(B)를 직접 생성하지 않고, 외부에서 주입해주는 방식
public class A{
	@Autowired
	private B b;
}

의존성 주입의 방식 3가지

1. 필드 주입

  • 클래스의 멤버 변수 위에 @Autowired를 작성
public class TestController{
	@Autowired
	private TestService testService;
}

 

2. Setter 주입

  • Setter 메서드 위에 @Autowired를 작성하여 주입
public class TestController{
	private TestService testService;
    
	@Autowired
	public void setTestService(TestService testService){
		this.testService = testService;
	}
}

 

3. 생성자 주입

  • 생성자 위에 @Autowired를 작성하여 주입
public class TestController{
	private TestService testService;
	
	@Autowired
	public TestController(TestService testService){
		this.testService = testService;
	}
}

 

Spring 4.3버전 이후부터는 필드 주입과 Setter 주입을 지양하고 생성자 주입 지향
  • 이유 ⇒ 순환 참조
  • 순환 참조란 A가 B를 참조하고, B가 A를 참조하는 것을 의미하는 데 이런 경우 서버가 죽어버림
    • 필드주입, Setter 주입
      • 실행 시 Bean을 생성한 후, 주입하려는 빈을 찾아 주입
      • 객체 생성 시점에 빈을 준비하기 때문에 서로 참조하는 객체가 생성되지 않은 상태에서 해당 빈을 참조하기 때문에 오류가 발생
    • 생성자 주입
      • 생성자의 인자에 사용되는 빈을 찾거나 빈 팩토리에서 생성
      • 무조건 빈을 생성하는 것이 아니라 주입하려는 빈이 있다면 그것을 사용, 없을 경우 빈을 생성하기 때문에 순환 참조가 일어나지 않는다고 함

순환 참조 추가 설명
@Service
public class AService {
    @Autowired
    private BService bService;
}

@Service
public class BService {
    @Autowired
    private AService aService;
}

순환 참조 발생 이유

  1. 빈 생성 과정
    • Spring은 애플리케이션 컨텍스트를 초기화할 때 @Service 애노테이션이 붙은 클래스들을 빈으로 생성
      @Service 이외에도 @Component, @Controller, @Repository도 빈 생성
  2. AService 빈 생성
    • AService 빈을 생성하려고 시도(시도일 뿐 생성이 아님)
    • AService 빈은 BService를 필요로 하므로 BService 빈의 인스턴스를 주입하려고 함
      • BService의 빈이 생성되는 것을 대기하는 것으로 이해
  3. BService 빈 생성
    • BService를 주입하기 위해 Spring이 BService 빈을 생성하려고 시도(시도일 뿐)
    • 그러나 BService는 AService를 필요로 하기 때문에 AService 빈의 인스턴스를 주입하려고 함
  4. 순환 참조 발생
    • AService는 BService의 빈이 필요, BService는 AService의 빈이 필요
      • 즉, 서로가 서로를 필요로 하여 무한정 대기하는 상태로 이해가 되며, 이것은 마치 서로의 자원을 다 쓸 때까지 기다리는 DeadLock 상태와 비슷한 케이스라고 이해가 됨


  • 따라서, 생성자 주입을 지향하며 생성자 주입 시 다음의 장점을 가짐
    1. 순환 참조 방지
    2. 테스트 코드 작성 용이
      • 원하는 객체를 생성한 후 생성자에 넣음
    3. 코드 악취 제거
    4. 객체 변이 방지
      • final을 선언함으로써 주입된 객체의 불변성을 보장
  • 생성자 주입의 단점
    • 주입해야할 것이 늘어날 때마다 생성자에 계속 추가해야 하기 때문에 매우 번거로움
public class Test{
	private final A a;
	private final B b;
	private final C c;
	private final D d;
	@Autowired
	public Test(A a, B b, C c, D d){
		this.a = a;
		this.b = b;
		this.c = c;
		this.d = d;
	}
}
  • 위의 단점으로 인하여 @RequiredArgsConstructor라는 애노테이션이 등장
    • 반드시 final로 선언해야만 주입이 됨
@RequiredArgsConstructor
public class Test{
	private final A a;
	private final B b;
	private final C c;
	private final D d;
}

결론

  • 의존성 주입(DI)는 필드 주입 / Setter 주입 / 생성자 주입 세 가지로 나뉘지만 순환 참조 같은 심각한 에러와 각종 편의성 등의 장점을 가진 생성자 주입을 사용하는 것을 권장
  • 생성자 주입의 주입해야할 객체가 N개일 경우 생성자의 매개변수 개수, 대입하는 코드가 N개만큼 추가가 되기 때문에 @RequiredArgsConstructor 등장
  • 주의 사항은 멤버 필드 앞에 final을 사용하지 않으면 의존성 주입이 되지 않기 때문에 유의할 것
    • @RequiredArgsConstructor를 사용하고 final을 붙이지 않을 경우 다음과 같이 NullPointException 발생