Spring 프레임워크는 3가지 핵심 프로그래밍 모델을 지원하고 있는데 그 중 하나가 의존성 주입이다.
의존성 주입이란 외부에서 두 객체 간 관계를 설정해주는 디자인 패턴으로, 인터페이스 사이에 둬서 클래스 레벨에서는 의존관계가 고정되지 않도록 하고 런타임 시에 관계를 동적으로 주입 하여 유연성을 확보하고 결합도를 낮출 수 있게 해준다.
의존성이란 한 객체가 다른 객체를 사용할 때 의존성이 있다고 한다. 예를 들어 다음과 같이 Store 객체가 Pencil 객체를 사용하고 있는 경우에 우리는 Store 객체가 Pencil 객체에 의존성이 있다고 표현한다.
public class Store{
private Pencil pencil;
}두 객체 간 관계(의존성)을 맺어주는 것을 의존성 주입이라고 하며 생성자 주입, 필드 주입, 수정자 주입 등 다양한 주입 방법이 있다. 스프링 4 이후부터는 생성자 주입을 권장하고 있다.
[생성자 주입의 장점]
- 생성자 호출 시점에 딱 한번만 호출되는 것을 보장하여 불변하게 설계가 가능(필드를 final로 선언 가능)
- 생성자 주입을 사용하면 의존성 주입 누락을 컴파일 단계에서 막을 수 있다.
- 필드 주입, 수정자 주입과는 다르게 객체 생성시점에서 순환참조를 방지할 수 있다.
- 테스트 코드를 순수 자바로 테스트 하는 것이 가능하다. (테스트에 용이)
예를 들어 연필이라는 상품과 1개의 연필을 판매하는 Store 클래스가 있다고 하자.
public class Store {
private Pencil pencil;
public Store() {
this.pencil = new Pencil();
}
}이 코드의 문제점은 다음과 같다.
- 두 클래스가 강하게 결합되어 있음
- 객체들 간의 관계가 아니라 클래스 간의 관계가 맺어짐
- 위 코드와 같은 Store 클래스는 현재 Pencil 클래스와 강하게 결합되어 있다는 문제점이 있다.
- Store에서 Pencil이 아닌 Food와 같이 다른 상품을 취급하자고 할 때 Store 클래스에 생성자 변경이 이루어지며 유연성이 떨어진다.
- 다른 상품을 판매하기 위해 생성자만 다르고 나머지는 중복되는 Store 클래스를 파생하는 것은 좋지 않다.
-
위 코드의 Store와 Pencil은 객체들 간의 관계가 아니라 클래스들 간의 관계가 맺어져 있다는 문제가 있다.
-
올바른 객체지향적 설계라면 객체들 간 관계가 맺어져야 한다.
-
객체들 간에 관계가 맺어졌다면 다른 객체의 구체 클래스(Pencil인지 Food인지 등)를 전혀 알지 못하더라도 (Interface를 구현한 상태라면) 인터페이스의 타입으로 사용할 수 있다. (구현에 의존하면 안되고 추상화에 의존해야 한다)
-
결국 위 같은 문제점이 발생한 근본적인 이유는 Store에서 불필요하게 어떤 제품을 판매할 지에 대한 관심이 분리되지 않았기 때문이다. Spring에서는 DI를 통해 이를 해결했다.
위 같은 문제를 해결하기 위해서는 다형성이 필요하다. Pencil과 Food 등 여러 가지 제품을 하나로 표현하기 위한 Interface 인 Product가 필요하다.
public interface Product {
}
public class Pencil implements Product {
}Store와 Pencil이 강하게 결합되어 있는 기존 코드를 수정해주어야 한다. 이를 제거하기 위해서는 다음과 같이 외부에서 상품을 주입(Injection)받아야 한다. 그래야 Store에서 구체 클래스에 의존하지 않는다.
public class Store {
private Product product;
public Store(Product product) {
this.product = product;
}
}여기서 Spring의 DI 컨테이너를 사용하는 이유를 알 우 있는데, 우선 Store에서 Product 객체를 주입하기 위해선느 애플리케이션 실행 시점에 필요한 객체(Bean)을 생성해야 하며, 의존성이 있는 두 객체를 연결하기 위해 한 객체를 나머지 객체로 주입시켜야 하기 때문이다.
코드처럼 Pencil이라는 객체를 만들고 그 객체를 Store로 주입시켜주는 역할을 위해 DI 컨테이너가 필요하다.
public class BeanFactory {
public void store() {
// Bean의 생성
Product pencil = new Pencil();
// 의존성 주입
Store store = new Store(pencil);
}
}이러한 개념은 제어의 역전(Inversion of Control, IoC)라고 불리기도 한다.
어떠한 객체를 사용할지에 대한 책임은 프레임워크에게 넘어갔고, 자신은 수동적으로 주입받는 객체를 사용하기 때문이다.
한 객체가 어떤 객체(구체 클래스)에 의존할 것인지는 별도의 관심사이다. Spring은 의존성 주입을 도와주는 DI 컨테이너로써, 강하게 결합된 클래스들을 분리하고, 애플리케이션 실행시점에 객체 간 관계를 설정해줌으로써 결합도를 낮추고 유연성을 확보한다.
이러한 방법은 상속보다 유연하지만 한 객체가 다른 객체를 주입받기 위해서는 반드시 DI 컨테이너에 의해 관리되어야 한다. 때문에 의존 관계를 계속해서 생성하고 소멸하는 데는 오버헤드가 발생하기 때문에 Spring에선 기본적으로 Bean을 싱글톤(Singleton)으로 관리한다.
- 두 객체 간의 관계라는 관심사의 분리
- 두 객체 간의 결합도를 낮춤
- 객체의 유연성을 높임
- 테스트 작성을 용이하게 함
참조 :

