본문 바로가기

Back-End/토비의 스프링3

1.1-IoC 컨테이너 : 빈 팩토리와 애플리케이션 컨텍스트

IoC 컨테이너 : 빈 팩토리와 애플리케이션 컨텍스트

스프링의 핵심 개념 중 하나인 IoC 컨테이너와 DI 기술에 대해 살펴보자.

스프링 애플리케이션에서는 객체의 생성, 사용, 제거, 관계설정 등을 애플리케이션 코드 대신 독립된 컨테이너가 담당한다. 코드 대신 객체에 대한 제어권을 갖고 있다고 해서 IoC(제어의 역전)이라 부른다.

IoC 컨테이너 = 빈 팩토리 또는 애플리케이션 컨텍스트

  • 빈 팩토리 : 객체의 생성과 객체 사이의 런타임 관계를 설정하는 DI 시점에서는 빈팩토리라 함
  • 애플리케이션 컨텍스트 : 앤터프라이즈 애플리케이션을 개발하는데 필요한 몇가지 기능이 추가된 것

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

스프링 컨테이너는 ApplicationContext 인터페이스의 구현체이다. 이 인터페이스를 보면 BeanFactory를 상속받은 것을 확인할 수 있다.


IoC 컨테이너를 이용해 애플리케이션 만들기

IoC 컨테이너를 만드는 방법은 아래와 같이 Applicationcontext 구현 클래스의 인스턴스를 만들면 된다.

// Ioc 컨테이너 생성, 생성과 동시에 컨테이너로 동작한다.
StaticApplicationContext ac = new StaticApplicationContext();

이로써 IoC 컨테이너가 준비되었지만 아무일을 하지 않는 빈 컨테이너일 뿐이다. POJO 클래스와 설정 메타정보가 더 필요하다.


IoC 컨테이너 사용하기 위해 필요한 것 - 1. POJO클래스

특정 기술과 스펙에 독립적이고 의존관계에 있는 다른 POJO와 느슨한 결합을 갖도록 하나의 클래스를 만든다.
public class Hello {

private String name;
private Printer printer;

public String sayHello() {
return "Hello " + name;
}

/**
* 1. 구체적으로 어떤 방식으로 출력할지는 몰라도 됨.
* Printer interface를 사이에 두고 느슨하게 연결되어 있음
* => 출력 방식이 변경되어도 Hello 객체
* 수정하지 않아도 됨 2. IoC 컨테이너가 실제 Printer 구현체와 연결해 준다.
*/
public void print() {
this.printer.print(sayHello());
}

public void setName(String name) {
this.name = name;
}

/**
* Printer 인터페이스 구현체를 DI 받는다.
* @param printer
*/
public void setPrinter(Printer printer) {
this.printer = printer;
}
}

위와 같이 각자 기능에 충실하게 독립적으로 POJO 클래스를 만들고, 인터페이스를 이용하여 결합도를 낮추는 것까지가 IoC 컨테이너가 사용할 POJO를 준비하는 첫번째 단계이다.


IoC 컨테이너 사용하기 위해 필요한 것 - 2. 설정 메타정보

방금 생성한 POJO 클래스를 애플리케이션에서 무엇을 사용할지, IoC컨테이너가 어떻게 관리 설정해주는 작업이다.

IoC 컨테이너의 기본적인 역할은 객체를 생성하고 이를 관리하는 것이다. 

스프링 컨테이너가 관리하는 이 객체들을 빈(Bean) 이라고 부른다.

이제 이 빈을 어떻게 만들고 어떻게 동작하게 할 것인가에 관한 정보를 설정할 것이다. 보통 XML로만 설정한다고 생각하는데 어떠한 형태로 설정해도 상관이 없다.

스프링 설정 메타정보는 어떠한 형태로 지정해도 상관없다? 왜?

스프링의 설정 메타정보는 BeanDefinition으로 만들어진 메타정보를 담은 객체를 이용하여 IoC와 DI 작업을 수행한다. 그렇기 때문에 파일 포맷이나 형식에 제한되거나 종속되지 않는 것이다.

IoC 컨테이너가 사용하는 빈 메타 정보는 다음과 같다.
  • 빈 아이디, 이름, 별칭 : 빈 오브젝트를 구분할 수 있는 식별자
  • 클래스 또는 클래스 이름 : 빈으로 만들 POJO 클래스 또는 서비스 클래스 정보
  • 스코프 : 싱글톤, 프로토타입과 같은 빈의 생성 방식과 존재 범위
  • 프로터피 값 또는 참조 : DI에 사용할 프로퍼티 이름과 값 또는 참조하는 빈의 이름
  • 생성자 파라미터 값 또는 참조 : DI에 사용할 생성자 파라미터 이름과 값 또는 참조할 빈의 이름
  • 지연된 로딩 여부, 우선 빈 여부, 자동와이어링 여부, 부모 빈 정보, 빈팩토리 이름 등

IoC 컨테이너를 사용하기 위해 필요한 2가지를 모두 구현하면 아래와 같은 방식으로 애플리케이션이 만들어진다.

IoC 컨테이너를 통해 애플리케이션이 만들어지는 방식

스프링 애플리케이션 = POJO 클래스 + 설정 메타정보를 이용해 IoC 컨테이너가 만들어주는 오브젝트의 조합


IoC 컨테이너가 생성되고 등록된 오브젝트를 가져오는 예시를 살펴보자.


@Test
public void registerBean() {
// Ioc 컨테이너 생성, 생성과 동시에 컨테이너로 동작한다.

// StaticApplicationContext는 테스트용으로 사용하기에 적당하다.
StaticApplicationContext ac = new StaticApplicationContext();

// Hello 클래스를 hello1이라는 이름의 싱글톤 빈으로 컨테이너에 등록한다.
ac.registerSingleton("hello1", Hello.class);

// IoC 컨테니어가 등록한 빈을 생성했는지 확인하기 위해 빈을 요청
Hello hello1 = ac.getBean("hello1", Hello.class);
assertThat(hello1, is(notNullValue()));
}


직접 BeanDefinition 타입의 설정 메타정보를 만들어서 IoC 컨테이너에 등록해보기

BeanDefinition helloDef = new RootBeanDefinition(Hello.class);
helloDef.getPropertyValues().addPropertyValue("name", "Spring");
ac.registerBeanDefinition("hello2", helloDef);


@Test
public void register_with_beanDefinition() {
// Ioc 컨테이너 생성, 생성과 동시에 컨테이너로 동작한다.
StaticApplicationContext ac = new StaticApplicationContext();

BeanDefinition helloDef = new RootBeanDefinition(Hello.class);

// ConsolePrinter 클래스 타입이며 printer 이름을 가진 빈 등록
ac.registerBeanDefinition("printer", new RootBeanDefinition(ConsolePrinter.class));

// 빈에 대한 프로퍼티 등록
helloDef.getPropertyValues().addPropertyValue("name", "Spring");
helloDef.getPropertyValues().addPropertyValue("printer", new RuntimeBeanReference("printer"));

ac.registerBeanDefinition("hello2", helloDef);

Hello hello2 = ac.getBean("hello2", Hello.class);

assertThat(hello2.sayHello(), is("Hello Spring"));
assertThat(ac.getBeanFactory().getBeanDefinitionCount(), is(2));

hello2.print();

}

이제 IoC 컨테이너가 POJO클래스와 설정 메타정보를 이용해 어떻게 최종 사용할 애플리케이션 런타임 오브젝트를 만들어내는지 이해할 수 있다. IoC 컨테이너는 빈 오브젝트가 생성되고 관계가 정의되면 그 뒤로는 거의 관여하지 않는다. 


IoC 컨테이너의 종류와 사용 방법

StaticApplicationContext

테스트 검증할 때만 사용. 실전에서는 사용하면 안 된다.

GenericApplicationContext

가장 일반적인 애플리케이션 컨텍스트(IoC 컨테이너)의 구현 클래스이다.  위의 클래스와 달리 XML과 같은 외부 리소스에 있는 빈 설정 메타정보를 읽어올 수 있다.

빈 설정 메타정보는 BeanDefinitionReader 인터페이스가 읽어온다.  XML을 읽어올 때는 XmlBeanDefinitionReader, 프로퍼티 파일에서 읽어오는 경우에는 PropertiesBeanDefinitionReader를 사용한다. 

GenericApplicationContext gac = new GenericApplicationContext();
XmlBeanDefinitionReader reader =new XmlBeanDefinitionReader(gac);
reader.loadBeanDefinitions("filelocation/context.xml");
ac.refresh(); // 모든 메타정보 등록 완료되었으니, 애플리케이션 컨테이너를 초기화하라는 명령

특정 파일에 종속되지 않고 유연하게 Reader를 바꿀 수 있다. 이는 스프링 컨테이너에서도 스프링이 지지하는 객체지향적인 설계를 적용하고 있다는 것을 알 수 있다.

GenericXmlApplicationContxt

위와 같은 코드가 필요없이 생성자만 만들면 reader, refresh() 가 설정되고 호출된다.
GenericXmlApplicationContext gxac= new GenericXmlApplicationContext("filelocation/context.xml");

WebApplicationContext

  • 스프링 애플리케이션에서 가장 많이 사용되는 애플리케이션 컨텍스트(IoC 컨테이너)이다.
  • 웹 환경에서 사용할 때 필요한 기능이 추가된 것 ( 대부분 서블릿 기반의 독립 WAR로 만들어짐 )
  • 사용 방법을 이해하기 위해 IoC 컨테이너를 적용했을 때 애플리케이션을 기동시키는 방법에 대해 알아볼 필요가 있음
    • 독립 자바 프로그램은 자바 VM에게 main() 메소드를 호출해달라 함
    • But, 웹에서는 main 메소드를 호출할 수가 없다. 사용자가 여럿이며 동시에 웹 어플리케이션을 사용한다.
      => 서블릿 컨테이너가 브라우저로부터 오는 HTTP 요청을 받아서 해당 요청에 매핑되는 서블릿을 실행해주는 방식으로 동작한다. ( 서블릿이 일종의 main 메소드 역할 )

웹 환경에서 스프링 애플리케이션이 기동하는 방식

  1. main() 메소드 역할을 하는 서블릿 만들기
  2. 미리 애플리케이션 컨텍스트 생성
  3. 요청이 서블릿으로 들어올때 마다 getBean()으로 필요한 빈을 가져옴


DispatchServlet

스프링에서 웹 환경에서 애플리케이션 컨텍스트를 생성하고 설정 메타 정보를 초기화해주고, 클러이언트로 들어오는 요청마다 적절한 빈을 만들어서 찾아서 이를 실행해주는 기능을 가진 DispatchServlet이라는 이름의 서블릿을 제공한다.

 web.xml에만 이 서블릿을 등록해주면 웹 환경에서 스프링 컨테이너(IoC 컨테이너)가 만들어지고 애플리케이션을 실행할 수 있다.


 



'Back-End > 토비의 스프링3' 카테고리의 다른 글

1.3-IoC 컨테이너 : 프로토타입과 스코프  (3) 2018.10.02
1.2-IoC 컨테이너 : 빈 설정하기  (0) 2018.09.22
스프링 공부방법  (0) 2018.09.16
#6. AOP  (0) 2017.08.29
#5. 서비스 추상화  (0) 2017.08.17