DI를 통해 의존을 처리하면 변경에 유연한 코드를 구현할 수 있다. 객체를 사용하는 클래스가 여러개라도 변경할 곳은 의존 주입 대상이 되는 객체를 생성하는 코드 한 곳 뿐이기 때문이다.
의존성이 줄어든다. = 변경에 덜 취약해진다.
모의 객체를 주입할 수 있어 단위 테스트가 쉬워진다.
가독성이 높아진다.
재사용성이 높아진다.
DI 방법
생성자 주입 (권장)
생성자 호출 시 외부로부터 의존성을 받는 방법
생성자 주입을 사용하면 필드를 final로 만들 수 있고 불변을 보장할 수 있다.
생성시 의존성을 바로 주입하므로 NullPointerException을 방지할 수 있다. 컴파일 타임에 순환 참조를 순환 참조도 방지할 수 있다.
생성자의 파라미터 개수가 많을 경우 각 인자가 어떤 의존 객체를 설정하는지 알아내기 위해 생성자의 코드를 확인해야 한다는 단점이 있다.
Setter 주입
의존성을 입력 받는 setter 메서드 사용
빈(empty) 생성자나 빈 정적 팩토리 메서드가 필요하다. 때문에 final 필드를 만들 수 없고 불변을 보장할 수 없다. 의존성을 선택적으로 주입하고 싶거나 수정할 필요가 있을 때 유용하지만 NPE가 발생할 가능성이 존재한다.
Interface 주입
의존성을 받는 메서드를 포함한 인터페이스를 작성하고 이 인터페이스를 구현하여 실행 시 메서드를 사용한다.
setter 주입처럼 외부에서 메서드 호출을 통해 의존성을 주입받는 것은 같지만, 인터페이스를 통해 구현을 강제한다는 차이가 있다.
객체의 관계를 일일이 설정해야 한다.
의존성 분리 : DIP 이용
상위계층이 하위계층에 의존하는 상황을 Interface를 이용해 분리한다.
Spring DI
적절하게 책임과 관심이 다른 코드를 분리하고, 서로 영향을 주지 않도록 다양한 추상화 기법을 도입하고, 애플리케이션 로직과 기술/환경을 분리하는 등의 작업은 갈수록 복잡해지는 엔터프라이즈 애플리케이션에는 반드시 필요하다. 이를 위한 핵심적인 도구가 바로 스프링이 제공하는 DI다. 스프링의 DI가 없었다면 인터페이스를 도입해서 나름 추상화를 했더라도 적지 않은 코드 사이의 결합이 남아있게 된다.
스프링이 DI에 담긴 원칙과 이를 응용하는 프로그래밍 모델을 자바 엔터프라이즈 기술의 많은 문제를 해결하는 데 적극적으로 활용하고 있기 때문이다. 또, 스프링과 마찬가지로 스프링을 사용하는 개발자가 만드는 애플리케이션 코드 또한 이런 DI를 활용해서 깔끔하고 유연한 코드와 설계를 만들어낼 수 있도록 지원하고 지지해주기 때문이다.
→ 자바를 객체 지향, 프로그래밍 원칙에 맞게 쓰기 위해 스프링(di)을 사용한다.
Spring DI 방법
필드 주입
주입받고자 하는 필드 위에 어노테이션을 붙인다.
하지만 필드 주입을 사용하면 테스트 등의 이유로 수동으로 의존성 주입을 하고 싶어도 하지 못한다. 따라서 의존성을 주입받아야 하는 객체가 프레임워크에 강하게 종속된다.
setter 주입
setter 메서드에 어노테이션을 붙인다.
생성자 주입
생성자 위에 어노테이션을 붙인다. 생성자가 하나인 경우 생략 가능하다.
IoC (Inversion of Control, 제어의 역전)
IoC를 구현하는 방법 중 하나가 DI
제어: 객체의 생명주기(객체의 생성, 초기화, 소멸 등)나 메서드의 호출을 직접 제어(관리)하는 것
제어의 역전: 프로그램의 제어 권한을 직접 제어하지 않고 외부에서 관리, 다른 대상에게 위임한다.
스프링과 같은 프레임워크를 사용할 때를 생각해보자. Controller, Service 같은 객체들의 동작을 직접 구현하긴 하지만, 어느 시점에 호출될 지는 신경쓰지 않는다. 프레임워크가 요구하는대로 객체를 생성하면 프레임워크가 해당 객체들을 가져다 쓰고 객체의 생명주기를 관리한다. 프로그램의 제어권이 프레임워크로 역전된 것이다.
왜 필요할까?
프로그램의 진행 흐름과 구체적인 구현을 분리시켜 개발자가 비즈니스 로직에 집중할 수 있다.
객체 내에서 제어하는 경우 변경에 취약하다. 인터페이스를 인자로 받으면 구체적인 클래스와 상관없이 인자를 받을 수 있다.
→ 응집도는 높아지고 결합도는 낮아져 변경에 유연해진다.
Bean
스프링이 IoC 방식으로 관리하는 객체
스프링이 직접 생성과 제어를 담당하는 객체
Bean Factory
스프링의 IoC를 담당하는 핵심 컨테이너
빈을 등록하고, 생성하고, 조회하고, 반환하는 등 빈을 관리하는 기능을 담당
Application Context
스프링에서 빈 팩터리를 확장한 IoC 컨테이너
빈 팩터리는 주로 빈의 생성과 제어의 관점에서 이야기하는 것
애플리케이션 컨텍스트는 스프링이 제공하는 애플리케이션 지원 기능을 모두 포함
Servlet Container
Servlet 관리를 담당하는데, 기본적으로 Dispatcher Servlet 하나만 만든다.
Container or IoC Container
IoC 방식으로 빈을 관리한다는 의미(bean의 생명주기 관리)에서 애플리케이션 컨텍스트나 빈 팩터리를 말한다.
DI를 사용하지 않았을 때의 문제점
0. dao, service 등을 static 키워드를 통해 구현한다.
객체지향적이지 않아 변경에 취약하다.
dao나 service를 교체하려면 객체를 직접 생성하고 다른 dao로 변경해서 다시 컴파일해야 한다.
DI & IoC
용어 정리
DI (Dependency Injection, 의존성 주입)
DI 방법
Interface 주입
상위계층이 하위계층에 의존하는 상황을 Interface를 이용해 분리한다.
Spring DI
→ 자바를 객체 지향, 프로그래밍 원칙에 맞게 쓰기 위해 스프링(di)을 사용한다.
Spring DI 방법
IoC (Inversion of Control, 제어의 역전)
왜 필요할까?
객체 내에서 제어하는 경우 변경에 취약하다. 인터페이스를 인자로 받으면 구체적인 클래스와 상관없이 인자를 받을 수 있다.
→ 응집도는 높아지고 결합도는 낮아져 변경에 유연해진다.
Bean
Bean Factory
Application Context
Servlet Container
Container or IoC Container
DI를 사용하지 않았을 때의 문제점
0. dao, service 등을 static 키워드를 통해 구현한다.
1. 생성자 주입
2. 인터페이스로 생성자 주입
DI 컨테이너를 사용하여 문제점들을 모두 해결하자!
DI Container