Spring Framework의 근간은 "Dependency Injection (의존성 주입)"에 있다. 이것은 도대체 어떤 것일까? 그 기본적인 사용법을 배우고, DI의 기능을 설명한다.


DI는 "의존성"을 분리

DI ( Dependency Injection, 의존성 주입) 란?

Spring Framework는 "DI 컨테이너 '라는 프레임워크로 시작했었다. DI는 "의존성 주입"이라는 기능이다.

프로그램은 다양한 기능을 컴포넌트화하여 이용하는 경우가 많다. 구성 요소에 각종 속성 등을 설정하여 이용하는 것이다. 이 때, 세세한 설정을 모두 코드로 작성하여 두면, 추후 변경이나 테스트 등이 매우 복잡하게 된다.

이 구성 요소의 설정 등과 같이 특정 상황 등으로 구성되는 것을 '의존성'이라고 한다. 이 의존성이 있기 때문에 코드가 특정 상황에서만 사용할 수 형태가 되어 버린 것이다.

따라서 구성 요소의 설정 정보 등의 의존성을 코드에서 분리하고 외부에서 주입하도록 하자는 것이 "의존성 주입"의 기본적인 생각이다. 이것은 몇 가지 방법이 있는데, 기본은 "Bean 및 설정 파일"로 프로그램을 만들 것이라고 생각하면 이해하기 쉬울 것이다.

Bean은 다양한 값 등을 속성으로 가지고 있는 간단한 클래스이다. 일반적으로 Bean 인스턴스를 생성하여 각종 속성 등을 설정하여 사용한다. 여기서 이 설정 처리 (의존성 부분)을 코드에서 분리 될 수 있다면 코드도 심플해 지고 테스트도 쉽고 될 것이다.

Spring Framework는 의존성 부분을 XML 파일에 기술해서 작성해서 준비하고 이를 가져 와서 자동으로 Bean 인스턴스를 생성 할 수 있다. 그 밖에도 어노테이션을 이용하는 방법도 있다만, Bean 설정 파일을 이용하는 방법이 가장 기본적 것으로 기억해 두자.

생성자를 이용한 의존성 삽입 (Constructor Injection)

  • 생성자를 이용한 클래스 사이의 의존 관계를 연결
  • Spring 프레임워커의 빈 설정 파일에서 constructor-arg 사용

setter() 메소드를 이용한 의존성 삽입 (Setter Injection : type 2 IoC)

  • Spring 프레임워커의 빈 설정 파일에서 property 사용


인터페이스 및 Bean 클래스 생성

그러면 실제로 간단한 예제를 만들면서 DI의 기본을 설명하고 가자. 우선 Bean 클래스를 만들자. 이번에는 하나의 메시지를 보관하는 단순한 Bean을 마련하기로 하자.

지난번 만든 프로젝트 "MySpringApp"의 com.devkuma.spring 패키지 아래 목록 란에 게재 된 인터페이스와 클래스를 작성하자.

SampleBeanInterface은 Bean의 내용을 정의하는 인터페이스이다. 여기에 메시지를 교환 할 getMessage / setMessage 두 가지 방법만 사용할 수 있도록 한다.

SampleBeanInterface인터페이스

package com.devkuma.spring;
 
public interface SampleBeanInterface {
    public String getMessage();
    public void setMessage(String message);
}

이를 구현한 클래스가 SampleBean이다. message라는 String의 속성과 toString 메서드를 재정의 하였다. 아무런 특색도 없는 단순한 Bean 이다.

"이렇게 간단한 것인데 어째서 인터페이스에서 만들지 않으면 안 돼?"라고 생각했을지도 모른다. Spring Framework의 Bean 이용은 별도 인터페이스에서 만들지 않아도 사용할 수 있다. 단, Bean의 일반적인 사용법이 이미지될 수 있도록, 이번에는 인터페이스부터 만들어 두었다.

SampleBean 클래스

package com.devkuma.spring;

public class SampleBean implements SampleBeanInterface {
   private String message;
    
   public SampleBean() {
       message = "(no message)";
   }
    
   public SampleBean(String message) {
       this.message = message;
   }

   public String getMessage() {
       return message;
   }

   public void setMessage(String message) {
       this.message = message;
   }

   @Override
   public String toString() {
       return "SampleBean [message=" + message + "]";
   }
}

Bean설정 파일 작성

그럼, Bean을 이용하기 위한 설정 파일을 작성하다. 프로젝트의 "src"폴더에 있는 "main"폴더에 "resources"폴더를 만들고, 이 속에 Bean 설정 파일을 만들자.

아래 내용 그에 대한 예이다. 이것을 작성하고 "bean.xml"라는 이름으로 "resources"폴더에 저장하자.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bean1" class="com.devkuma.spring.SampleBean">
        <property name="message" value="Hello, this is Bean Sample!!" />
    </bean>
 
</beans>

이 Bean 설정 파일은 <beans>라는 태그 내에 <bean> 태그를 사용하여 Bean의 정보가 들어 간다. 이것은 의미는 아래와 같다.

<bean id="이름" class="클래스">
    <property name="속성 이름" value="값"/>
    ...... 필요한 만큼 <property>를 추가 ......
</bean>

이번 SampleBean에는 message라는 속성이 하나 준비되어 있다. 그래서 name="message"의 <property> 태그를 하나 준비했다. 여기서 속성에 설정되는 값의 정보를 넣어 둔다. 이렇게 하면 여기에 기술된 속성 값이 설정된 Bean 인스턴스를 자동으로 생성할 수 있게 되는 것이다.



응용 프로그램에서 Bean 이용

그럼 bean.xml에 정의 된 Bean을 응용 프로그램에서 이용해 보자. MySpringApp의 com.devkuma.spring 패키지에 "App.java"를 만들고, 아래처럼 소스 코드를 작성한다. 실행을 하면 SampleBean을 println하고 "Hello, this is Bean Sample !!"라고 표시된다.

package com.devkuma.spring;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class App {
 
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("bean.xml");
        SampleBeanInterface bean1 = (SampleBeanInterface)app.getBean("bean1");
        System.out.println(bean1);
    }
 
}

실행 결과는 아래와 같다.

903, 2017 4:38:49 오후 org.springframework.context.support.AbstractApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@5ce65a89: startup date [Sun Sep 03 16:38:49 KST 2017]; root of context hierarchy
903, 2017 4:38:49 오후 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
정보: Loading XML bean definitions from class path resource [bean.xml]
903, 2017 4:38:50 오후 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
정보: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@79b4d0f: defining beans [bean1]; root of factory hierarchy
SampleBean [message=Hello, this is Bean Sample!!]

이처럼 실행한거 간단하게 설명을 하겠다.

1. Bean 설정 파일에서 ApplicationContext를 생성한다.

ApplicationContext app = new
    ClassPathXmlApplicationContext("bean.xml");

Bean 이용의 기본은 먼저 "ApplicationContext"라는 클래스의 인스턴스를 취득하는 것이다. 이 클래스는 이름 그대로 응용 프로그램의 컨텍스트를 관리한다. 이 경우 컨텍스트는 이를테면 "Bean"이라고 생각해도 좋다.

이 ApplicationContext를 만들려면 몇 가지 방법이 있는데, 그 하나는 Bean 설정 파일 (방금 만든 bean.xml)를 읽어 들여 그것을 바탕으로 작성하는 것이다. Bean 설정 파일에서 생성되는 ApplicationContext는 ClassPathXmlApplicationContext라는 클래스가 된다. 이것은 ClassPathXmlApplicationContext의 서브 클래스에서 XML 파일을 처리하는 기능이 추가된 것이다. 인수는 Bean 설정 파일 이름을 지정한다.

2. Bean를 취득하기

SampleBeanInterface bean1 = (SampleBeanInterface) app.getBean ( "bean1");

ApplicationContext 인스턴스가 준비되었다면, 다음은 간단하다. "getBean"메소드를 호출하는 것뿐이다. 이것은 인수에 지정한 이름의 Bean 인스턴스를 꺼내는 것이다. 먼저 bean.xml를 만들 때 <bean id = "bean1"...>라고 쓴 것을 기억하라. 이 id로 지정된 값이 getBean 인수에 사용된다.

이렇게 추출된 Bean은 일반 인스턴스와 동일하게 사용할 수 있다. 주목해야 하는 것은 Bean에는 이미 message 속성의 값이 설정되어 있다는 점이다. bean.xml에는 <property> 태그를 기술하고 있다.

이것은 bean.xml 값을 써서 변경하는 것만으로 소스 코드를 전혀 변경없이 사용하는 SampleBean의 내용을 바꿀 수 있다는 것이다. 이것이 "의존성 주입"이라는 것이다. 이는 다시 말하면 Bean을 사용하는 코드에 대해 일절 변경없이, 외부에서 Bean의 내용을 조작이 가능하다는 것이다.


다른 Bean 추가

이것으로 의존성 주입의 기본적인 구조는 알았다. 한 걸음 더 전진하여 다른 Bean을 만들어 이용해 보기로 하자.

아래와 같이 간단한 샘플을 작성해 보겠다. 이번에는 "SomeBean"라는 클래스를 만들어 보자. 역시 SampleBeanInterface을 implements해서 message 속성을 가진다. 그러나 실제로는 String 타입의 message라는 필드는 존재하지 않는다. 내부에는 Date 및 SimpleDateFormat를 필드로 보관해 두었다가, 일시적으로 텍스트를 message로 교환 할 수 있도록 하고 있다.

package com.devkuma.spring;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class SomeBean implements SampleBeanInterface {
    private Date date;
    private SimpleDateFormat format;

    public SomeBean() {
        date = Calendar.getInstance().getTime();
        format = new SimpleDateFormat("yyyy/MM/dd");
    }

    public String getMessage() {
        return format.format(date);
    }

    public void setMessage(String message) {
        try {
            date = format.parse(message);
        } catch (ParseException e) {
            e.printStackTrace();
            date = null;
        }
    }

    @Override
    public String toString() {
        return "SomeBean [date=" + format.format(date) + "]";
    }
}

클래스가 준비되면 bean.xml을 열고 먼저 기술한 <bean> 태그 부분을 아래와 같이 고쳐보자.

<bean id="bean1" class="com.devkuma.string.SomeBean">
    <property name="message" value="2017/9/3"/>
</ bean>

이제 실행하면 출력되는 텍스트가 "SomeBean [date=2017/09/03]" 바뀐다. SomeBean 인스턴스가 생성되어 사용할 수 있도록 되어있는 것을 알 수 있을 것이다. App 소스 코드에는 일절 손대지 않았는데 말이다.

903, 2017 4:56:10 오후 org.springframework.context.support.AbstractApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@5ce65a89: startup date [Sun Sep 03 16:56:10 KST 2017]; root of context hierarchy
903, 2017 4:56:10 오후 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
정보: Loading XML bean definitions from class path resource [bean.xml]
903, 2017 4:56:10 오후 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
정보: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@79b4d0f: defining beans [bean1]; root of factory hierarchy
SomeBean [date=2017/09/03]

이 예제와 같이 인터페이스를 정의하고 구현 클래스를 여러 준비해두면 간단히 속성 값을 설정할 뿐만 아니라, 그 속성의 처리 방법 등을 자유롭게 변경할 수 있게 되었다. 사용하는 클래스 및 속성 값은 코드를 전혀 건드리지 않고 변경할 수 있다. 이제 "Bean 인스턴스를 설정 파일에서 자동으로 생성한다"는 방식의 장점을 알게 되었다.

'Spirng' 카테고리의 다른 글

[Spring] Spring Data JPA 이용  (0) 2017.12.10
[Spring] AspectJ 이용  (0) 2017.12.10
[Spring] AOP 이용  (0) 2017.12.10
[Spring] 어노테이션으로 DI 구현  (1) 2017.12.10
[Spring] Dependency Injection(DI)와 Bean  (0) 2017.12.10
[Spring] Spring 프로젝트 생성  (0) 2017.12.10

+ Recent posts