Study/Spring

[Spring Framework] 스프링 DI란?

AC 2019. 8. 6. 21:44

 

스프링 DI

스프링은 MVC 프레임워크(스프링 MVCa)와 JDBC를 추상화한 프레임워크(스프링 JDBC) 등 여러 기능을 개발자에게 제공하는 애플리케이션 아키텍처의 베이스가 된다. 그리고 코어가 DIxAOP 컨테이너이다.

DI란?

우선 스프링이 제공하는 DIxAOP컨테이너에서의 DI 부분을 알아보자.
DI는 인터페이스를 이용해 컴포넌트화를 실현하는 것이다. 이 부분을 분명히 인식해야 한다.

DI를 우리말로 옮기면 의존 관계의 주입이다. 의미가 구체적이지 않아 선뜻 이해되지 않겠지만, 쉽게 말해 오브젝트 사이의 의존 관계를 만드는 것이다. 이 말은 어떤 오브젝트의 프로퍼티(인스턴스 변수)에 그 오브젝트가 이용할 오브젝트를 설정한다는 의미이다. 이를 학술적으로 말하면, 어떤 오브젝트가 의존(이용)할 오브젝트를 주입 혹은 인젝션(프로퍼티에 설정)한다는 것이다. 

그리고 DI는 단순히 이러한 인젝션을 가리키지만, DI를 구현하는 컨테이너는 이 밖에도 클래스의 인스턴스화 등의 생명 주기 관리 기능이 있는 경우가 많다. 인터페이스를 이용하지 않는 단순한 예를 드렁 설명하면, 제품(product)을 관리하는 웹 애플리케이션을 모방한 예제 애플리케이션을 생각해보자. 


 



간단히 동작시키기 위해 우선 main 메서드가 있는 ProductSampleRun 클래스를 작성한다. 이 클래스는 웹 애플리케이션의 뷰(View)와 컨트롤러(Controller)를 대신한다고 생각해보자.

계속해서 비즈니스 로직을 실현하는 ProductService 클래스를 준비하고 데이터베이스 액세스를 하는 ProductDao 클래스를 작성한다.
데이터베이스 액세스 오브젝트를 DAO(Data Access Object)라고 부르고 클래스명은 XxxDao 형식으로 작성한다.

다음 샘플은 복잡한 기능을 제외하기 위해서 실제 데이터베이스는 하지 않는다. 가장 간단하게 만들며, ProductSampleRun이 ProductService를 new하고 ProductService가 ProductDao를 new한 다음 각각의 인스턴스를 생성해 이용하는 형태일 것이다.

이어서 DI 컨테이너를 이용하는 예이다. DI 컨테이너를 이용하면 ProductSampleRun이 이용하는 ProductService의 인스턴스, 그리고 ProdutService가 이용하는 ProductDao의 인스턴스는 DI 컨테이너가 생성해준다. 그리고 ProductDao의 인스턴스를 이용하는 ProductService에 인젝션(의존 관계 주입) 해준다.

 

 

main 메서드가 있는 ProductSampleRun은 DI 컨테이너의 생성이나 ProductService의 인스턴스 취득 등을 해야 하지만, 이러한 작업은 실제 웹 애플리케이션에서는 코드에서 배제된다. 우선 간단한 예제 애플리케이션이라고 생각하고 생략한다.

DI 컨테이너가 다루는 ProductService 클래스, ProductDao 클래스에는 DI를 위한 특수한 메커니즘이 필요하다. 다시 말해, DI 컨테이너가 인스턴스를 생성할 클래스, 인스턴스를 전달받을 클래스는 모두 POJO(Plan Old Java Object)(컨테이너나 프레임워크에 독립적인 자바 오브젝트)로 작성해도 문제가 없다.

지금까지의 설명만으로는 DI 컨테이너의 장점은 클래스에서 new 연산자가 사라지는 것뿐, 다시 말해 POJO를 사용해 단순해 POJO를 인스턴스화 하지 않기 위한 기술이고, 인터페이스 기반의 컴포넌트화(지금까지 설명해온 부품화)와는 관계가 없어 보이기도 하다.

하지만 클래스에서 new 연산자가 사라졌다는 사실이 중요하다.
클래스에서 new 연산자가 사라짐으로써 개발자가 팩토리 메서드 같은 디자인 패턴을 구사하지 않아도 DI 컨테이너가 건네주는 인스턴스를 인터페이스로 받아서 인터페이스 기반의 컴포넌트화를 구현할 수 있게 됐다.

※ 지금부터 기술하는 스프링이 제공하는 어노테이션을 사용하면 프로그램은 스프링에 의존해버리고 POJO가 아니라고 생각하지만, 일반적으로 어노테이션은 의존이라고 생각하지 않는 것 같아서 이 포스팅도 그에 따른다.
또한, 어노테이션을 사용하지 않고 Bean 정의 파일만 사용하면 진짜 POJO로 하는 것도 가능하다.

 

DI를 이용할 때는 원칙적으로 클래스는 인터페이스에 의존하고 실현 클래스에서는 의존하지 않을 필요가 있다. 그러므로 위 그림과 같은 설계는 원칙적으로는 잘못된 것이다.

앞의 예에서 ProductService와 ProductDao라는 구현 클래스를 이용해 설명했지만 인터페이스 기반의 컴포넌트화를 실현하려면 ProductService와 ProductDao(라는 이름)를 인터페이스로 하고, 그 구현 클래스는 인터페이스 이름에 Impl를 덧붙인 것으로 한다. DI 컨테이너를 이용할 때는 이러한 인터페이스 기반의 컴포넌트화를 의식해 설계할 필요가 있다.

DI 컨테이너의 구상 클래스의 인스턴스화는(디폴트로는) 1회만 실행한다. 생성된 인스턴스는 필요한 곳에서 사용한다. 이렇게 하는 것으로 서비스와 DAO처럼 Singleton으로 만들고 싶은 컴포넌트를 특별히 Singleton으로 만들지 않아도 간단히 실현되게 해준다.

이것으로 웹 애플리케이션을 본뜬 샘플 애플리케이션은 완성이다.
인터페이스와 DI 컨테이너를 이용함으로써 부품화의 이점을 누릴 수 있으며, 이것으로 개발 효율이 상승하고, 변경과 확장에 강하며 품질이 좋은 애플리케이션이 된다.

마지막으로 샘플의 동작 시퀀스 다이어그램을 이해해보자. 다음 그림은 샘플의 시퀀스 다이어그램(정확하지는 않지만 알기 쉽게 기술)이다.

시퀀스 No : 1~1.1

이클립스 혹은 Spring Tool Suite(STS)에서 ProductSampleRun의 main 메서드를 기동하면 execute 메서드가 호출된다.

시퀀스 No : 1.1.1 ~ 1.1.2


execute 메서드에서 DI 컨테이너가 생성되고 DI 컨테이너의 getBean 메서드가 ProductService(실제로는 ProductServiceImpl)를 취득한다.

시퀀스 No : 1.1.3 ~ 1.1.3.1


"100원 공책"이라는 Product 인스턴스를 생성하고 ProductService의 addProduct 메서드, Product(실제로는 ProductDaoImpl)의 addProduct 메서드를 이용해 저장한다(이 예제 코드에서는 ProductDaoImpl이 데이터베이스에 접속하지 않는다. 소스를 확인해보면 알 수 있듯이 HashMap에 저장하고 있다.)

시퀀스 No : 1.1.4 ~ 1.1.4.1


계속해서, ProductService의 findByProductName 메서드, ProductDao의 findByProductName 메서드를 이용해서 저장해둔 Product 인스턴스를 취득한다.

시퀀스 No : 1.1.5


취득한 Product 인스턴스의 내용을 표준 출력으로 출력한다.

 

 

 

 

LIST