DI 방법
의존성 주입 방법에는 다양한 방법이 존재한다.
- 필드 주입
- 수정자(setter) 주입
- 생성자 주입
각 방법의 장단점과, 생성자 주입을 사용해야하는 이유를 알아보자.
필드 주입
필드에 @Autowired 어노테이션만 붙여주면 자동으로 의존성 주입이 된다.
@Controller
public class Controller{
@Autowired
private Service service;
}
코드가 간결한 것이 장점이지만, 반대로 그렇기 때문에 의존관계를 파악하기 힘들다.
또한 외부에서 접근이 불가하며, 필드가 final 변수일 경우 @Autowired 어노테이션을 쓸 수 없다.
수정자(setter) 주입
Setter 메소드에 @Autowired 어노테이션을 붙이는 방법이다.
@Controller
public class Controller{
private Service service;
@Autowired
public setService(Service service){
this.service = service;
}
}
setter 메서드는 public 이기 때문에, 객체 변경이 어디서든 가능해진다는 단점이 있다.
필드 주입과 수정자 주입은 의존성이 있는 객체가 생성되지 않아도
이를 포함하고 있는 객체가 생성 가능(컴파일 시 오류 미발생)하여 런타임 시에 오류가 발생할 수 있다.
또한 의존성 주입 없이 사용 시 객체를 사용하는 부분에서 NullPointerException 발생 가능성이 존재한다.
또한 선언과 함께 초기화되어야 하는 final을 사용할 수 없다.
생성자 주입
생성자에 @Autowired 애노테이션을 붙여 의존성을 주입받는 방법이다.
Spring 4.3 부터 클래스의 생성자가 하나이고, 그 생성자로 주입받을 객체가 Bean으로 등록되어 있다면 @Autowired를 생략할 수 있다.
Spring 프레임워크에서 권장하는 방법이다.
@Controller
public class Controller{
private Service service;
@Autowired
public Controller(Service service){
this.service = service;
}
}
그렇다면 Spring 프레임워크에서 생성자 주입 방식을 권장하는 이유는 무엇일까?
NPE 방지 및 컴파일 시 오류 감지
생성자에서 의존관계 주입이 일어나기 때문에, 객체가 생성 될 때 의존 객체의 null 여부를 검사하므로 컴파일 시에 오류를 감지 할 수 있다
또한 null을 주입하지 않는 한 NullPointerException은 발생하지 않는다.
final 키워드 사용 가능
final 변수는 생성자에서 초기화가 가능하기 때문이다.
순환 참조 방지 (in Spring)
예를 들어, 닭 객체와 달걀 객체의 순환 참조를 필드 주입 방식으로 구현해보자.
@Component
public class Chicken {
@Autowired
Egg egg;
public void layEgg(){
egg.becomeChicken();
}
}
닭은 달걀을 낳고, 달걀은 닭이 된다.
@Component
public class Egg {
@Autowired
Chicken chicken;
public void becomeChicken() {
chicken.layEgg();
}
}
달걀은 닭이 되고, 닭은 달걀을 낳는다.
위와 같이 서로가 서로의 객체를 참조하며 실행되는 메서드가 수행되면, 무한루프 상태가 되어 StackOverflow 에러가 발생하게 된다.
이 문제가 필드/수정자 주입에서는 런타임 시 발견되지만, 생성자 주입 시에는 컴파일 시 발견 가능하다.
이는 객체의 생성 시점인 애플리케이션 구동 시점에 에러가 발생하기 때문이다.
(Spring Bean 등록 시 객체를 생성하는 과정에서 순환참조 발생)
new Chicken(new Egg(new Chicken(new Egg()...)))
reference