[Spring] IoC Container

IoC 컨테이너

  • IoC Container는 IoC를 관장하는 컨테이너이다.
  • IoC (Inversion of Contrl)
    • 제어 역전을 뜻하는 것으로 Framework이 제공하는 성질 중 하나이다
    • 일반적으로 프로그램을 작성할 때 프로그램이 흘러가는 흐름이나 객체 제어권은 개발자에게 있으나, 이를 프레임 워크에 위임하는 것을 뜻함
    • 프레임워크 사용자(개발자)는 정해진 틀(Frame) 안에서 동작이나 방법들을 상세화하는 방법으로 소프트웨어를 구축하게 된다
  • Spring Framework 에서는 xml 또는 자바 어노테이션을 이용해 이를 지원한다

POJO Class

  • POJO (Plain Old Java Object) : 자바 모델이나 기능, 프레임워크 등에 따르지 않고 홀로 독립적이며 단순한 기능만 가진 객체들을 의미한다
  • 자바에서는 이러한 객체들을 Bean 이라고 부른다.
  • 학생 정보를 관리하거나 학교 정보들을 관리하는 객체들이 예시가 된다.
  • POPO(PHP), POCO(.NET Framework), PODS(C++), POD(Perl) 등이 있다.

이러한 Java POJO Classes 들은 개발자가 설정한 Metadata 를 이용해 Spring Container 에서 생성된다.

IoC 컨테이너의 종류

IoC 컨테이너의 종류는 BeanFactory 와 ApplicationContext 두 가지로 나뉜다. 각각은 다음과 같은 특징을 가진다.

BeanFactory

  • 클래스를 통해 객체를 생성하고 객체의 주소값을 반환하는 방식으로 전달한다.
  • 상속 등 객체 간의 관계를 형성하고 관리한다.
  • Bean에 관련된 설정을 위한 xml 파일은 즉시 로딩되지만 객체는 개발자가 요구할 때 생성한다.
  • 옛날에 쓰이던 기술로 현재는 많이 사용되지 않음
  • 사용 형태
    • XmlBeanFactory

먼저 BeanFactory 활용 예제를 알아보자. 다음과 같이 TestBean 클래스를 작성하고 간단한 생성자를 만든다.

package com.springpractice.beans;

public class TestBean {
	public TestBean(){
		System.out.println("생성 완료");
	}
}

이후 MainClass 파일에서 BeanFactory 를 생성해본다.

위에서도 설명했지만 BeanFactory는 개발자가 요구할 때 객체를 생성하기 때문에 xml 파일이 로드되어도 객체를 생성하지 않으면 생성자 동작하지 않는다. 아래 코드에서 주석 부분을 포함 또는 배제해가며 그 차이를 확인할 수 있다.

또 한 가지 재미있는 점으로 동일한 id 값을 사용해 TestBean 을 호출해도 생성자는 한 번만 호출된다는 것이다. 즉, Container 는 기존에 생성한 Class 주소 한 가지만을 포함하고 있다.

package com.springpratice.main;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import com.springpractice.beans.HelloWorld;
import com.springpractice.beans.TestBean;

public class MainClass {

	public static void main(String[] args) {
		test1();
	}

	// BeanFactory - 패키지 내부
	public static void test1() {
		ClassPathResource res = new ClassPathResource("com/springpractice/config/beans.xml");
		XmlBeanFactory factory = new XmlBeanFactory(res);

		/* ******************************************************** */
		TestBean t1 = factory.getBean("t1", TestBean.class);
		TestBean t2 = factory.getBean("t1", TestBean.class);
		/* ******************************************************** */
	}
}

패키지 외부에 xml 파일이 있을 때 사용하는 방법으로 FileSystemResource 가 있다. 기존 코드와 거의 동일하며 xml 파일이 존재하는 절대적 위치를 넘겨주는 차이가 있다.

package com.springpratice.main;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

import com.springpractice.beans.HelloWorld;
import com.springpractice.beans.TestBean;

public class MainClass {

	public static void main(String[] args) {
		test2();
	}

	// BeanFactory - 패키지 외부
	public static void test2() {
		FileSystemResource res = new FileSystemResource("beans.xml");
		XmlBeanFactory factory = new XmlBeanFactory(res);

		/* ******************************************************** */
		TestBean t1 = factory.getBean("t1", TestBean.class);
		TestBean t2 = factory.getBean("t1", TestBean.class);
		/* ******************************************************** */
	}
}

ApplicationContext

  • 클래스를 통해 객체를 생성하고 객체의 주소값을 반환하는 방식으로 전달한다.
  • 상속 등 객체 간의 관계를 형성하고 관리한다.
  • 국제화 지원 등 문자열에 관련된 다양한 기능을 제공한다.
  • 리스너로 등록된 Bean에 이벤트를 발생시킬 수 있다.
  • Bean 에 관련된 설정을 위한 xml 파일은 즉시 로딩하면서 객체를 미리 생성해 가지고 있다.
  • 사용 형태
    • ClassPathXmlApplicationContext
    • FileSystemXmlApplicationContext
    • XmlWebApplicationContext
    • AnnotationConfigApplicationContext

다음은 ApplicationContext 활용 예제다. 앞서 보았던 BeanFactory 예제 코드와 비슷하지만 여러 가지 옵션이 가능하다는 점에서 차이를 보인다. 로드와 동시에 객체를 생성하며, 같은 id로 클래스를 호출해도 다른 객체를 전닫받을 수 있도록 조절할 수 있다.

package com.springpratice.main;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;

import com.springpractice.beans.HelloWorld;
import com.springpractice.beans.TestBean;

public class MainClass {

	public static void main(String[] args) {
		test3();
	}

	// ApplicationContext - 패키지 내부
	public static void test3() {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("com/springpractice/config/beans.xml");
		ctx.close();
	}
}

위 코드를 실행하기만해도 생성자가 호출되는 것을 확인할 수 있다. 이를 BeanFactory 처럼 객체를 호출할 때 생성자가 호출되도록 바꾸고 싶다면 lazy-init="true" 옵션을 삽입해보자. 이외에도 다양한 bean 태그 기본 속성들과 이를 응용하는 방법들이 있다.

ctx로부터 여러 번 bean을 가져오는 getBean 을 호출하면 항상 같은 주소가 반환된다. 이를 바꿔주기 위해 xml 파일에 <bean id='testBean' class='com.springpractice.beans.TestBean' scope="prototype"></bean> 다음과 같이 선언해보자. scope 를 바꿔주는 것이다. 그리고 아래 코드를 실행해보면 서로 다른 주소 값이 반환되는 것을 확인할 수 있다.

public class MainClass {

	public static void main(String[] args) {
		test3();
	}

	// ApplicationContext - 패키지 내부
	public static void test3() {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("com/springpractice/config/beans.xml");
		TestBean tb1 = ctx.getBean("testBean", TestBean.class);
		TestBean tb2 = ctx.getBean("testBean", TestBean.class);
		
		System.out.printf("tb1 : %s\n", tb1);
		System.out.printf("tb2 : %s\n", tb2);
		ctx.close();
	}
}

/*
02:01:41.662 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@f5f2bb7
02:01:41.952 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 2 bean definitions from class path resource [com/springpractice/config/beans.xml]
02:01:41.993 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'com.springpractice.beans.TestBean#0'
생성 완료
생성 완료
tb1 : com.springpractice.beans.TestBean@56de5251
tb2 : com.springpractice.beans.TestBean@419c5f1a
02:01:42.047 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@f5f2bb7, started on Tue Jan 04 02:01:41 KST 2022
*/

Updated:

Leave a comment