우선 스프링은 Java에 기반하여 웹 어플리케이션을 개발할 때 사용하는 프레임워크로 다양한 개념들이 있는데, 그 중 DI, 즉 의존성 주입에 대해서 알아보겠습니다.
DI (Dependecy Injection) 이라고 불리는 의존성 주입은 new 연산자를 사용해서 객체를 생성하는 것과 같이 어떤 객체가 사용하는 의존 객체를 직접 만드는 것이 아니라 주입 받아서 사용하는 방법입니다. 다시 말해서, 외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴인데, 인터페이스를 사이에 둬서 클래스 레벨에서는 의존 관계가 고정되지 않도록 하고, 런타임 시에 관계를 동적으로 주입하여 유연성을 확보하고 결합도를 낮추는 방법입니다. 여기서 의존성이란? 한 객체가 다른 객체를 사용할 때 의존성이 있다고 합니다.
public class School {
private Teacher teacher;
}
이렇게 School이라는 객체가 Teacher라는 객체를 사용하고 있는 경우에, School 객체가 Teacher 객체에 의존성이 있다고 표현합니다. 이렇듯 의존 관계를 맺어주는 것을 의존성 주입이라고 합니다.
setter와 생성자를 이용해서 외부에서 주입해주는 방식은 새롭게 객체를 만들어주지 않아도 되기 때문에 더 유연한 상황이라고 할 수 있습니다.
의존성 주입에는 4가지 방법이 존재합니다.
- 생성자 주입
- 수정자 주입 (setter)
- 필드 주입
- 일반 메서드 주입
스프링 공식 문서에서는 생성자 주입을 권장하고 있습니다.
결론부터 말하자면
- 두 객체 간의 관계라는 관심사 분리
- 두 객체 간 결합도 낮춤
- 객체 유연성을 높임
- 테스트 작성에 용이
라는 장점이 있습니다.
public class School {
private Teacher teacher;
public School() {
this.teacher = new Teacher();
}
}
위 코드의 경우에는 1. 두 클래스가 강하게 결합되어 있고, 2. 객체 간의 관계가 아니라 클래스 간의 관계가 맺어진다는 문제점을 갖게 됩니다.
이 문제점은 Teacher이 아닌 새로운 클래스의 객체를 만들고자 한다면 School 클래스 생성자 변경이 필요하게 됩니다. 이 말은 유연성이 떨어진다는 의미가 됩니다.
School과 Teacher 모두 클래스이기 때문에 클래스 간의 관계가 맺어지게 됩니다. 객체지향적 설계에서는 객체들간의 관계가 맺어져야 합니다. 객체 간의 관계가 맺어지면 다른 객체의 구체 클래스 (ex. Teacher) 를 알지 못하더라도 인터페이스의 타입 (ex. People 등) 으로 사용할 수 있습니다.
우선 다형성을 통해서 하나로 묶을 수 있는 People 같은 인터페이스가 필요합니다. 이 인터페이스를 우선 구현해줍니다.
public interface People {
}
public class Teacher implements People {
}
이렇게 People 이라는 인터페이스를 만들었습니다. 앞선 코드에서 School과 Teacher가 강하게 결합되어 있는 부분을 제거해주기 위해서 외부에서 People을 주입 받아야 School이 다른 구체 클래스에 의존하지 않게 됩니다.
public class School {
private People people;
public School(People people) {
this.people = people;
}
}
이렇게 수정하게 됩니다. 여기서 People 객체를 주입하기 위해서는 애플리케이션 실행 시점에 필요한 객체 (Bean, 빈)을 생성해야 합니다. 이를 위한 DI 컨테이너가 필요합니다.
public class BeanFactory {
public voide school() {
// Bean 생성
People people = new People();
// DI (의존성 주입)
School school = new School(people);
}
}
(전 이 시점에서 딱. 이해해버렸습니다.)
이렇게 DI 컨테이너를 통해서 의존성 주입을 해줄 수 있습니다.