💋 의존성 주입(DI)이란?
의존성 주입 = Dependency Injection = DI
먼저 '의존성'이 무엇인지부터 알아보자!
✔ 의존성(Dependency)
기업에서 사용하는 애플리케이션은 하나의 객체로 이루어져 있지 않다.
완전 간단한 애플리케이션이더라도, 몇 개의 객체가 서로 협력하고 있다.
객체가 협력한다는 것은 객체 간의 의존성이 존재한다는 것이다.
의존성이란 파라미터나 리턴값 또는 지역변수 등으로 다른 객체를 참조하는 것을 의미한다.
이렇게 하나의 객체는 다른 객체의 부품이 된다.
이 글에서 말할 의존성 주입은 Spring의 의존성 주입이다.
Spring에서 관리해주는 객체를 Spring Bean이라고 하는데, 주로 Controller, Service, Repository를 스프링 컨테이너에서 관리하게 된다. 따라서 의존성 주입도 스프링 컨테이너에서 관리하는 객체들을 주입하는 과정이라고 생각하면 편하다.
뜬금없이 내가 만들었던 domain 내의 객체를 @Autowired를 사용해서 주입하면 안된다...!
💋 의존성을 주입하는 방법
크게 3가지 방법이 있다.
- 생성자 주입 (Constructor Injection)
- setter 주입 (Setter Injection)
- 필드 주입 (Field Injection)
하나씩 알아보고, 장단점을 뜯어보자
✔ 생성자 주입 (Constructor Injection)
@Service
public class StationConstructorService {
private final StationRepository stationRepository;
@Autowired
public StationConstructorService(final StationRepository stationRepository) {
this.stationRepository = stationRepository;
}
public String sayHi() {
return stationRepository.sayHi();
}
}
생성자를 통해 의존관계를 주입받는 방법으로, 우리가 일반적으로 사용해 오던 자바 코드와 딱히 다를 것 없다.
- 정적 팩토리 메서드를 사용해서 생성자를 호출하더라도 그 결과는 동일하다.
- 생성자를 호출할 때 딱 1번만 호출되기 때문에 stationRepository 변수를 final로 관리할 수 있다.
- 생성자 주입 사용 시, 생성자가 1개인 경우 @Autowired를 생략할 수 있다.
[개인적 궁금증] @Autowired를 생략할 수 있는 경우에 생략하는 것이 더 좋을까?
처음에는 Spring의 DI를 사용하는 코드에서 @Autowired가 붙어있지 않은 생성자를 만난다면 혼란스러울 수 있다고 생각해 생성자가 1개인 부분에서 모두 @Autowired 어노테이션을 사용했었다. 리뷰어에게 어노테이션을 생략해도 될 것 같다는 리뷰를 받았고, 어째서 생략하는 것이 더 좋은지에 대해서 생각해 보았다.
- 제거하는 쪽이 편리하다. 어노테이션을 붙이는 것보다는 붙이지 않는 것이 더 편리하다.
- 순수 자바 코드로도 잘 작동할 수 있기 때문에, 굳이 DI 프레임워크에 의존성을 가질 필요가 없다.
✔ setter 주입 (Setter Injection)
@Service
public class StationSetterService {
private StationRepository stationRepository;
@Autowired
public void setStationRepository(final StationRepository stationRepository) {
this.stationRepository = stationRepository;
}
public String sayHi() {
return stationRepository.sayHi();
}
}
필드의 값을 변경하는 setter 메서드를 통해서 의존 관계를 주입하는 방법이다.
- 변경 가능성이 있는 의존 관계에 사용한다.
- 생성자 호출 이후에 필드 변수에 변경이 일어나야 하므로, stationRepository 변수에 final 제어자를 붙일 수 없다.
✔ 필드 주입 (Field Injection)
@Service
public class StationFieldService {
@Autowired
private StationRepository stationRepository;
public String sayHi() {
return stationRepository.sayHi();
}
}
필드에 그대로 주입하는 방법이다.
- 코드가 간결하다는 것이 가장 큰 장점이다.
- 하지만, 클래스 외부에서 접근이 불가능해 테스트하기 어렵다는 단점이 있다.
- 또한 이 코드는 우리가 기존에 작성하던 자바의 코드와는 매우 이질적이다. 위의 생성자 주입, setter 주입의 방식은 Spring 프레임워크의 DI를 사용하지 않아도 작동하는 반면, 이 코드는 DI 프레임워크가 없으면 사용할 수 없게 된다.
따라서 사용하지 않는 것을 권장한다. 실제로 필드 주입 방식을 사용하면 IDE에서 아래와 같은 warning을 만날 수 있다.
💋 생성자 주입을 사용하자!
위에서 세 가지 방법에 대해서 설명했지만, 생성자 주입을 권장한다.
이유에 대해서는 아래에 세 가지 방식을 자세히 재 보았다.
- 생성자 주입 방식
- 우선, 대부분의 의존 관계는 애플리케이션 종료까지 변할 일이 없는 경우가 많다.
- 객체를 생성할 때 한 번 생성자를 호출해 의존 관계를 정의하고, 불변으로 설계할 수 있다.
- 순수 자바 코드에서도 파라미터 중 하나라도 누락된 경우에 곧바로 컴파일 예외가 발생해 잘못된 설계에 대해서는 컴파일 조차 하지 않고 문제점을 발견할 수 있다.
- DI 프레임워크에 의존하지 않고, 순수 자바 언어로도 잘 작동하며 자바 언어의 객체 지향이라는 특징을 잘 살릴 수 있다.
- setter 주입 방식
- setter 메서드를 public으로 설정해 놓으면, 의존 관계를 외부에서 자유롭게 수정할 수 있는데, 안정성에 위협이 된다.
- DI가 없이 순수한 자바 코드를 사용할 때 setter를 모두 호출해 필드에 값을 넣을 것을 강제할 수 없다. 따라서 객체를 생성한 후 setter를 호출하지 않은 채로 다른 메서드를 호출하게 되면, 누락된 필드의 변수는 null로 남아있어 NullPointerException이 발생하게 된다.
- 필드 주입 방식
- DI 프레임워크에 가장 크게 의존해, 순수 자바 언어로는 무용지물이다.
- 테스트하기조차 어렵기 때문에 비교할 것도 없이 사용하지 않는 편이 좋다.
결론은...! 생성자 주입 방식을 사용하자!
하지만 만약 내가 의존성을 추가하고자 하는 변수가 필수가 아닌 선택이라면 setter 주입 방식으로 의존성을 주입해 두 가지 장점을 모두 가져갈 수도 있을 것이다! (유연하게)
💋 참고자료