purplely88's home

DI란

드디어 스프링 코어인 DI에 대해서 설명해보겠습니다.

 

DI는 Dependency Injection (의존성 주입)의 약어입니다.

DI는 사용함에 있어서는 매우 간단하지만 스프링 전체의 기반이기 때문에 너무나 중요합니다. 

 

이해를 돕기위해 간단한 코드를 기재합니다만, 직접 작성하여 컴파일 할 필요는 없습니다.

 

DI를 한 단어로 설명하자면..

DI가 무엇인가에 대해 한 단어로 설명하자면, 인스턴스 관리입니다.

@AutoWired 어노테이션을 필드쪽에 붙이면 DI 컨테이너로부터 인스턴스를 취득합니다.

 

이미지

점점 그림이 추악해진다...

인스턴스 관리라고 말하면 막연하기 때문에 아래의 두가지를 한다고 생각해주세요.

 

1. 인스턴스의 생성

DI 컨테이너 안에서 인스턴스를 생성합니다. 즉, 클래스를 new 합니다.

그리고 애플리케이션에서는 그것들의 인스턴스를 취득하여 이용합니다.

 

DI안에서 매회 new를 한 인스턴스를 애플리케이션에 넘길 것인가, 아니면 한번 new 한 인스턴스를 애플리케이션에 넘길 것인가와 같은 것도 관리해줍니다.

 

2. 인스턴스의 라이프 사이클 관리

인스턴스의 라이프 사이클 관리를 간단히 코딩할 수 있도록 되어있습니다.

이것은 웹 애플리케이션 개발을 더욱더 편리하게 해줍니다. 

 

예를들어 서블릿의 요청 스코프(request scope)나 섹션 스코프(session scope)에 인스턴스를 간단히 등록가능하다는 뜻입니다.

 

인스턴스의 생성과 라이프 사이클 관리(인스턴스 파괴)를 DI가 해주기때문에, 클래스를 new 하던지, 사용이 끝난 변수에 null을 넣을 필요가 없어집니다. 이러한 이점때문에 null 처리를 깜빡했을 경우를 방지해주며 코드의 가독성도 높아집니다.

 

이건 어디까지나 DI를 간단하게 설명한 것입니다. 

하지만 이것을 알고있는 것만으로도 다음 설명에 이해 정도가 다를 것입니다.

 

보충 : new

자바에서 클래스를 new 하는 것은 인스턴스가 메모리에 올라간다는 의미입니다.

그러므로 너무 많이 new 를 해버리면, 그 만큼 메모리를 사용하게 됩니다.

 

이것이 안좋다라는 의미는 아닙니다만, 같은 페이지에 대량의 요청을(request) 처리하게 된다면 같은 클래스를 그 만큼 new 할 필요가 있습니다.

 

더욱이, new 를 한 이후에는 기본적으로 그 변수에 null 을 입력합니다. null 을 입력함으로써, 가비지 컬렉션이 메모리상의 인스턴스를 깨끗하게 청소해주고 메모리를 비워주기 때문입니다. 

 

DI를 이용한다면 기본적으로 null을 입력할 필요가 없습니다.

null을 넣지않으면 안되는 경우도 분명히 있습니다만 그건 추후에 설명하겠습니다.

 

DI 설명 전에...

먼저 DI를 이해하기 전에 필수 지식부터 설명드리고 있습니다.

이것을 이해한다면 DI를 간단하게 이해할 수 있기 때문입니다.

 

DI에 대해서 이해하기 위해,  DI (의존성 주입)을 의존성과 주입. 이 두가지를 분석하겠습니다.

그리고 이 정도 이해가 되신다면 DI에 대해서 거진 다 이해하실 수 있으실 겁니다.

 

의존성이란 무엇인가

애초에 자바에 있어 의존이라는게 무엇인가? 라는 것부터 설명하겠습니다.

의존이라고 하는 것은 다른 클래스를 이용하고 있는가와 같은 것 입니다.

 

1. 다른 클래스를 로컬 변수로써 가지고 있다.

2. 다른 클래스가 메서드의 파라미터, 반환 값으로 되어있다.

 

자동차를 예를 들어 설명해보겠습니다.

자동차에는 엔진이 있습니다. 물론 타이어라던지 여러가지도 있습니다만, 이해하기 편하게 엔진만 가지고 있는 자동차라고 가정해보겠습니다. 이 때의 코드는 아래와 같을 것입니다.

 

자동차 클래스 (현대)

public class Car {

 

 // 현대 엔진

 private HyundaiEngine hyundaiEngine;

 

 // 생성자

 public class Car(HyundaiEngine hyundaiEngine) {

   // 현대 엔진 설정

   this.hyundaiEngine = hyundaiEngine;

 }

}

 

이 자동차 클래스는 현재 엔진을 사용합니다. 

그리고 이 자동차 클래스를 만드는 (new) 코드는 다음과 같습니다.

 

메인 클래스 (기본 생략)

  

  // 부품 생산

  HyundaiEngine hyundaiEngine = new HyundaiEngine();

  // 자동차 생산

  Car car = new Car(hyundaiEngine);

 

클래스 다이어그램으로 표현하자면...

일부 생략했습니다. 중요한 포인트는 따로있기때문에..

클래스 다이어그램에는 클래스간의 관계를 표현한 다이어그램입니다. 

클래스 다이어그램에는 의존을 나타낼때 점선 화살표를 이용하여 표현합니다.

 

자동차가 엔진을 사용하기 위해 자동차 클래스에는 현대 엔진 클래스를 의존하고 있습니다.

예를 들어, 자동차의 최고속도는 엔진에 의해 결정됩니다. 즉, 차의 성능은 엔진에 의존하고 있기때문입니다.

그렇기 때문에 의존원 자동차로부터 의존처의 엔진을 점선 화살표로 이어지게 됩니다.

 

이와 같이 어느 클래스가 다른 클래스를 이용하고 있는 것이 바로 의존입니다.

그리고 의존은 '정도'가 있습니다.

 

예를 들어, 아까와 같은 자동차에서는 엔진을 교환하기 위해서는 현대의 같은 엔진만 이용할 수 밖에 없습니다.

좀더 연비가 좋은 엔진이 있어도 교환이 안됩니다. 혹시나 이 자동차에 현대 엔진이 아닌 기아 엔진을 사용하게 된다면, 아래와 같이 수정할 수 밖에 없습니다.

 

자동차 클래스 (기아)

public class Car {

 

 // 기아 엔진

 private KiaEngine kiaEngine;

 

 // 생성자

 public class Car(KiaEngine kiaEngine) {

   // 기아 엔진 설정

   this.kiaEngine= kiaEngine;

 }

}

 

메인 클래스 (기본 생략)

  

  // 부품 생산

  KiaEngine kiaEngine= new KiaEngine();

  // 자동차 생산

  Car car = new Car(kiaEngine);

 

부품 일부를 바꾸는 것이 아닌 자동차 클래스와 메인 클래스 전부 수정하지 않으면 안됩니다.

이걸로는 간단하게 부품을 교체할 수가 없습니다. 또한, 자동차 클래스를 테스트할 때는 기아 엔진 클래스가 완성되지 않으면 테스트를 진행할 수 없습니다. 이대로라면 테스트도 어렵고 변경에 취약한 애플리케이션이 되버립니다.

이와 같이 의존도가 높은 상태를 tight coupling. 즉 결합이 밀접하다고 합니다.

 

여기서 의존도를 낮추는 방법에는 자바의 인터페이스를 사용합니다.

인터페이스를 사용함에 있어서 자동차의 부품을 자유롭게 교체할 수 있게 됩니다.

 

엔진 인터페이스

public interface Engine {

  public void boot();  // 기동 메서드

  public void stop();  // 정지 메서드

}

 

엔진의 인터페이스는 기동 메서드와 정지 메서드를 준비해 보겠습니다. 

어느쪽도 전 엔진에 필요한 메서드입니다. 인터페이스를 상속한 클래스에는 반드시 해당 메서드를 코딩하지 않으면(오버라이드) 안됩니다. 안그러면 컴파일 에러가 발생합니다.

 

엔진 클래스 (현대)

public class HyundaiEngine implements Engine {

  // 생성자

  public HyundaiEngine() {

    super();

  }

 

  // 기동 메서드

  @Override

  public void boot() {

    System.out.println("현대 엔진 기동");

 

  // 정지 메서드

  @Override

  public void stop() {

    System.out.println("현대 엔진 정지");

}

 

엔진 인터페이스를 상속한 현대 엔진 클래스에는 기동과 정지시의 구체적인 처리가 적혀져 있습니다. 

덧붙여서 인터페이스를 상속한 클래스를 서브 클래스라고 합니다.

 

자동차 클래스

public class Car {

 

 // 엔진

 private Engine engine;

 

 // 생성자

 public class Car(Engine engine) {

   // 기아 엔진 설정

   this.engine= engine;

 }

 

 // 시동을 건다

 public void engineStart() {

   engine.boot();

 }

 

 // 시동을 잠근다

 public void engineStop() {

    engine.stop();

  }

}

 

자동차 클래스에는 필드를 인터페이스로 변경합니다. 그리고 인터페이스의 기동, 정지 메서드를 이용하는 메서드를 준비합니다.

 

메인 클래스 (기본 생략)

  

  // 엔진 생산 (현대 엔진)

  Engine engine = new HyundaiEngine ();

 

  // 자동차 생산

  Car car = new Car(engine);

 

  // 시동을 건다

  car.engineStart();

 

  // 시동을 잠근다

  car.engineStop();

 

메인 클래스에서는 Engine형의 변수에 현대 엔진의 인스턴스를 넣고있습니다.

인터페이스형의 변수에는 상속한 서브클래스의 인스턴스를 넣는 것이 가능합니다.

 

엔진의 동작을 확인하기 위해, 시동을 걸고 정지합니다. 이 실행 결과는 다음과 같습니다.

 

결과

현대 엔진 기동

현대 엔진 정지

 

현대 엔진 클래스의 메서드가 실행되고 있다는 것을 알 수 있습니다.

여기서 다시 클래스 다이어그램으로 가보면..

 

슬슬 그림에 한계가 보인다..

자동차 클래스는 엔진 인터페이스를 의존하고 있습니다만, 현대 엔진에는 의존하고 있지 않습니다.

자동차 클래스의 점선은 엔진 인터페이스에만 향하고 있습니다. 

여기서 기아 엔진을 사용하고 싶을 경우에는 기아 엔진 클래스를 준비하여 메인 클래스를 조금 수정함에 있어 간단하게 끝납니다. 즉 자동차 클래스는 수정하지 않습니다.

 

엔진 클래스 (기아)

public class KiaEngine implements Engine {

  // 생성자

  public KiaEngine() {

    super();

  }

 

  // 기동 메서드

  @Override

  public void boot() {

    System.out.println("기아 엔진 기동");

 

  // 정지 메서드

  @Override

  public void stop() {

    System.out.println("기아 엔진 정지");

}

 

메인 클래스 (기본 생략)

  

  // 엔진 생산 (기아 엔진)

  Engine engine = new KiaEngine();

 

  // 자동차 생산

  Car car = new Car(engine);

 

  // 시동을 건다

  car.engineStart();

 

  // 시동을 잠근다

  car.engineStop();

 

메인 클래스에는 기아 엔진 클래스를 생성 (new) 하여 변경하고 있습니다.

그 결과는 아래와 같습니다.

 

결과

기아 엔진 기동

기아 엔진 정지

 

이와 같은 경우의 클래스 다이어그램을 다시 그리자면...

기아 엔진 추가

주목해야 할 부분은 기아 엔진을 추가해도 자동차 클래스의 의존은 오직 엔진 인터페이스만 의존하고 있습니다.

즉, 엔진 클래스를 추가하고 싶어도 자동차 클래스는 일절 변경을 하지않아도 된다는 것입니다. (오호!)

또한, 자동차 클래스의 테스트를 진행할려고 하면, 엔진 인터페이스를 상속한 더미 클래스를 준비하여 테스트를 진행할 수 있게 되었습니다.

 

이와 같이 인터페이스를 사용하면 의존'정도'를 낮출 수 있습니다. 

이와 같이 의존도가 낮은 상태를 loose coupling. 즉 결합이 느슨하다고 합니다.

 

자바의 인터페이스에 대한 얘기가 되었습니다만, 여기까지가 의존성의 얘기가 됩니다.

 

정리하자면...

1. 의존이라는 것은 타 클래스를 이용하고 있다는 것을 말한다.

2. 자바의 인터페이스를 사용함에 있어, 의존'정도'를 낮추는 것이 가능하다. (결합이 느슨하다.)

 

다음 포스트에서는 주입에 대해서 설명해보도록 하겠습니다.