의존성 주입(Dependency Injection, DI)은 객체지향 프로그래밍의 디자인 패턴 중 하나다.
객체지향 프로그래밍에서는 객체 간의 의존성이 발생할 수 있다. 즉, 한 객체가 다른 객체의 속성과 기능을 사용하기 위해서 의존하는 경우가 존재한다. 이러한 의존성은 객체 간의 결합도를 증가시키고 유지보수와 테스트를 어렵게 한다.
그래서 DI를 사용하게 된다. DI는 의존성을 외부에서 주입 받도록 만들어 객체 간의 결합도를 낮춘다. 즉, 객체가 직접 의존하는 객체를 생성하지 않고, 외부에서 생성된 객체를 주입 받아 사용하게 된다.
객체 생성 후, 필드를 이용해 의존하는 방법이다. 객체 생성 이후에도 의존성을 변경할 수 있기 때문에 유연성이 높지만, 객체의 상태를 외부에서 직접 변경할 수 있다는 문제가 있어 권장되지는 않는다.
public class MyService {
@Autowired
private MyRepository myRepository;
public void doSomething() {
myRepository.save();
...
}
위와 같이 @AutoWired 를 통해 주입 받을 수 있다.
객체 생성 후 세터 메서드를 사용해 의존하는 방법이다. 필드 주입과 같이 객체 생성 이후에도 의존성을 변경할 수 있기 때문에 유연성이 높지만, 필드 주입과 마찬가지의 이유로 권장되지 않는다.
public class MyService {
private MyRepository myRepository;
@Autowired
public void setMyRepository(MyRepository myRepository) {
this.myRepository = myRepository;
...
}
set 메서드를 통해 객체 내부 필드에 할당하도록 한다.
객체를 생성할 때 생성자를 통해 의존하는 방법이다. 객체를 생성하는 시점에서 의존성을 주입 받기 때문에 객체 생성 이후에는 불변한다. 그래서 런타임 환경에서 객체가 가변되는 일은 없다.
public class MyService {
private final MyRepository myRepository;
@Autowired
public MyService(MyRepository myRepository) {
this.myService = myService;
}
...
}
스프링 프레임워크에서는 생성자 주입을 권장하고 있다. 이는 위와 같이 불변 객체를 만들 수 있다는 점과 의존성 주입에 필요한 코드를 객체 내부에 숨길 수 있어, 가독성과 유지 보수성이 향상된다.
DI에 대해 단점이라고 하면, 객체를 생성하고 매핑하는 데 있어, 약간의 오버헤드가 발생한다고 얘기한다. 근데 별로 유의미하진 않는 것 같다.
스프링에서 DI를 사용하지 않을 경우에는 클래스 내부에서 new 연산자를 통해 생성자가 초기화될 때 객체를 생성해준다.