IoC, DI 컨테이너
오늘은 IoC, DI 컨테이너에 대해서 알아보고자 한다.
IoC, DI 컨테이너를 보기 전, 객체지향부터 확인하기
일단 IoC, DI 컨테이너를 보기 전 좋은 객체 지향 설계의 5가지 원칙에 대해 보는 것이 살펴보고 가자.
디자인 패턴과 SOLID 원칙
최근 객체 지향 프로그래밍을 자주 하게 되면서 해당 로직이 어떻게 더 깔끔하게 작성할지, 또는 코드의 유지보수성이 잘 되었으면 하는 생각에 여러 주제를 찾아보다가 디자인 패턴을 접하
jheaon.tistory.com
객체지향으로 코드를 작성하기 위해서는 다음과 같은 규칙을 따르는 것이 좋다.
- 역할과 구현을 분리한다.
- 클라이언트는 역할(인터페이스만 알면 된다)
- 클라이언트는 내부 구조와 구조의 변경이 일어나도 영향을 받지 않는다.
- 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다.
- 자바 언어의 다형성 활용을 이용한다.
- 역할 : 인터페이스
- 구현 : 인터페이스를 구현한 클래스, 구현 객체
- 객체를 설계할 때 역할과 구현을 명확하게 분리하여 역할(인터페이스)을 먼저 부여하고, 수행하는 구현 객체를 만든다.
DI
DI란 Dependency injection의 줄임말로 의존성 주입이라는 말을 가진다. 이게 처음에 의존성 주입이라는 말이 와닿지 않을 수 있는데 이를 아래의 예를 보며 설명하면 쉽게 이해할 수 있다.
- MemberService.java
public class MemberService {
private MemberRepository memberRepository = new MemoryMemberRepository();
}
현재 해당 클래스인 MemberService는 MemoryMemberRepository에 의존하고 있다. 하지만 만약 내가 레포지토리의 종류를 바꾸고 싶다면 어떻게 변경하여야 할까?
의존하다 : 한 클래스가 다른 클래스의 메서드나 데이터를 사용함을 의미한다. 예를 들어 클래스 A가 다른 클래스 B의 메서드를 실행할 때, A클래스가 B클래스에 의존한다고 표현할 수 있다.
- MemberService.java
public class MemberService {
// private MemberRepository memberRepository = new MemoryMemberRepository();
private MemberRepository memberRepository = new JdbcMemberRepository();
}
다음과 같이 수정이 가능하다. 하지만 해당 코드는 SOLID 원칙을 위배하고 있다.
- 구현 객체를 변경하려면 클라이언트 코드(여기서 클라이언트 코드는 MemberService 클래스를 의미한다.)를 변경해야 한다는 점에서 OCP 원칙을 위배하고 있다.
- 추상화가 아닌 MemoryMemberRepository와 JdbcMemberRepository처럼 구현 객체에 의존한다는 점에서 DIP 원칙을 위배하고 있다.
그럼 해당 원칙을 지키면서 코드를 작성하려면 어떻게 해야 할까? 이를 지켜 코드를 작성하면 다음과 같이 작성이 가능하다.
- MemberService.java
public class MemberService {
private MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}
}
MemberService는 더 이상 구현체에 의존하지 않고, MemberRepository라는 추상화에 의존하도록 코드를 작성하게 되었다. 이로 인해 다음 이점을 얻을 수 있다.
- 더 이상 구현 객체를 변경하기 위해서 MemberService의 코드 변경을 하지 않아도 된다. (OCP 원칙)
- 추상화에 의존하고 있다. (DIP원칙)
이제 MemberService 클래스를 사용하기 위해서는 MemberService에게 어떤 구현 객체를 인자로 넘겨주어야 한다. 이를 AppConfig.java 파일을 만들어 해당 책임을 맡게 한다.
- AppConfig.java
public class AppConfig{
public MemberService memberService(){
return new MemberService(MemoryMemberRepository());
}
}
이때 구현 객체를 넘겨 의존 관계를 만들어 주는 것을 의존성을 주입한다고 해서 DI라고 한다.
IoC
IoC란 Inversion of Control의 줄임말로 프로그램의 제어 흐름을 클라이언트가 직접 제어하는 것이 아는 외부에서 관리하는 것을 의미한다. 제어의 역전이라고도 말한다.
위에 AppConfig가 없던 코드는 클라이언트 구현 객체가 직접 제어 흐름을 관리(구현 객체 직접 생성 및 연결)를 했다면, AppConfig 등장 이후에는 구현 객체는 오로지 자신의 로직만 실행하는 역할을 담당하게 된다.
IoC / DI 컨테이너
Spring 프레임워크에서는 IoC / DI컨테이너라는 곳에서 객체의 모든 생명 주기(객체 생성, 관리, 책임)를 관리하고 있다. 즉 AppConfig의 역할을 Ioc / DI 컨테이너에서 해주고 있는 셈이다.
빈(Bean) : 스프링 컨테이너로 생성되는 객체를 의미한다.
Spring에서 컨테이너가 생성되는 과정은 다음과 같다.
// 스프링 컨테이너 생성 - 구성 정보로 AppConfig.class로 설정
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
ApplicationContext 인터페이스를 이용하여 AnnotationConfigApplicationContext 구현체를 만들어 사용한다. 이때 매개 변수로는 구성 정보를 전달한다.
스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용하여 스프링 빈을 등록한다.