요약 (다음 질문의 정답을 안다면 이 포스트를 읽지 않아도 된다.)
- IoC 컨테이너와 ApplicationContext는 완전히 같은 개념인가?
- IoC 컨테이너를 구성하는 방법은 어떤 것이 있는가?
- BeanFactory와 ApplicationContext의 차이를 알고 있는가?
IoC 컨테이너
스프링의 IoC 컨테이너는 객체를 인스턴스화하고 구성 및 조합하고 수명주기를 관리하는 역할을 한다.
스프링이 제공하는 IoC 컨테이너
스프링의 IoC 컨테이너는 두 가지 유형의 컨테이너를 제공한다.
- BeanFactory 기반 컨테이너
- ApplicationContext 기반 컨테이너
우리가 가장 흔히 아는 ApplicationContext는 IoC 컨테이너 중 하나이다.
BeanFactory는 IoC 컨테이너의 가장 기본적인 버전이다.
ApplicationContext는 BeanFactory의 기능을 확장한 버전이다.
BeanFactory
BeanFactory는 IoC 컨테이너의 가장 기본적인 버전이다.
BeanFactory는 메타데이터를 기반으로 빈 객체를 생성하고 구성한다.
이때 BeanFactory는 XML 기반으로 메타데이터를 사용한다.
BeanFactory는 Lazy Loadaing으로 빈 객체를 등록한다.
코드로 이해하기
User라는 POJO 클래스가 있다.
1 | public class User { |
이 클래스를 XML을 통해 Bean 등록해보자.
1 |
|
config.xml에 user라는 이름으로 빈 등록을 해줬다.
init-method를 통해 빈 객체가 생성될 때 User#setIsBeanInitialized를 실행하도록 했다.
만약 User가 빈 객체로 생성되면 IS_BEAN_INITIALIZED가 true가 될 것 이다.
테스트 코드로 확인해보자.
1 | class ContainerTest { |
여기서 Lazy Loading을 확인할 수 있다.
Lazy Loading은 BeanFactory에서 빈 객체를 가져올 때 해당 객체를 생성한다는 의미다.
그래서 User.IS_BEAN_INITIALIZED가 빈 객체를 가져올 때 true가 됨을 확인 할 수 있다.
ApplicationContext
ApplicationContext는 BeanFactory의 하위 인터페이스다.
따라서 BeanFactory의 모든 기능을 제공한다.
다만 ApplicationContext는 웹 어플리케이션, AOP에 필요한 더 많은 기능을 제공한다.
ApplicationContext의 선언부를 통해 어떤 기능을 추가로 제공하는 지 보자.
1 | public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver { |
- EnvironmentCapable 인터페이스 : 프로파일과 프로퍼티를 다루는 Environment를 접근할 수 있게 한다.
- ListableBeanFactory 인터페이스 : BeanFactory의 기능을 지원하기 위한 인터페이스. 빈 객체를 생성 관리한다.
- HierarachicalBeanFactory 인터페이스 : BeanFactory 구현체 사이의 계층 구조를 확인할 수 있게 한다.
- MessageSource 인터페이스 : 국제화(i18n)을 제공하는 인터페이스
- ApplicationEventPublisher 인터페이스 : 이벤트를 발생시키는 기능을 제공한다.
- ResourcePatternResolver 인터페이스 : 리소스를 읽어오는 기능을 제공한다.
ApplicationContext의 메타데이터 설정 방법들
BeanFactory와 달리 ApplicationContext의 중요한 기능 중 하나는 메타데이터를 다양한 방식으로 할 수 있다.
BeanFactory는 XML 외부 파일로 컨테이너를 설정해줘야 했다.
ApplicationContext는 어노테이션 기반 설정도 지원한다!
아까 사용했던 User POJO 클래스를 다시 사용하자.
1 | public class User { |
그리고 @Configuration 어노테이션을 붙인 설정 파일로 Bean 등록해보자.
1 |
|
@Bean 어노테이션이 붙은 메서드가 실행되면 반환되는 객체를 빈 객체로 컨테이너에 등록한다.
그리고 XML의 init-method 대신 메서드 안에 setIsBeanInitialized 메서드를 호출했다.
테스트로 확인해보자.
1 |
|
설정 클래스를 전달해서 ApplicationContext를 생성했다.
ApplicationContext는 eager-loading이라 컨테이너가 생성됐을 때 빈 객체를 생성한다!
그래서 빈 객체를 호출하기 전에도 이미 빈 객체가 생성되어 있다는 의미다!
(결국 모든 빈객체를 eager-loading하기 때문에 ApplicationContext는 비교적 무거운 IoC container이다.)
ComponentScan
ComponentScan은 특정 패키지에서 @Component가 붙은 클래스를 빈 객체 등록하는 방법이다.
일일히 빈 객체를 메서드로 작성하지 않아도 된다는 장점이 있다.
이전에 @Bean 어노테이션으로 등록했던 방식 대신 ComponentScan을 활용해보자.
먼저 빈등록하고 싶은 클래스에 @Component 어노테이션을 달아주자.
1 |
|
InitializeBean 인터페이스로 해당 클래스가 빈 객체로 만들어지고 나서 setIsBeanInitialized()를 실행하도록 했다.
이제 설정 파일에 @ComponentScan 어노테이션을 붙여주자!
1 |
|
@ComponentScan은 특정 패키지를 기준으로 해당 패키지와 하위 패키지의 @Component 어노테이션이 붙은 클래스를 빈등록한다. (위 예시는 User클래스가 있는 패키지를 기준으로 잡았다.)
@Bean을 붙여서 빈객체를 생성해줬던 메서드를 제거할 수 있게됐다!
그렇다면 스프링 부트에서는 어떻게 ComponentScan을 하는 걸까??
스프링 부트를 사용하면 @Configuration을 붙인 설정파일 없이도 @Component를 붙인 클래스를 빈등록 해준다.
왜 그렇게 되는 건지 확인해보자.
1 |
|
스프링 부트 프로젝트를 만들면 먼저 @SpringBootApplication이 붙은 클래스가 프로젝트 패키지에 생성된다.
@SpringBootApplication에 들어가보면 다음과 같다.
1 |
|
즉 @SpringBootApplication 어노테이션에는 @ComponentScan 어노테이션이 포함되어 있다.
basePackages가 따로 설정하지 않았으니 해당 클래스가 속한 패키지를 기준으로 컴포넌트 스캔을 진행하게 된다.
그리고 @Filter 어노테이션으로 특정 클래스들을 컴포넌트 스캔에서 제외할 수 있다.
Bean 생명주기
Bean 객체의 생명 주기를 이해하기 위해서는 Bean을 생성하고 관리하는 스프링 컨테이너의 생명주기를 이해해야 한다.
스프링 컨테이너의 생명주기
- 컨테이너 초기화
- 컨테이너 종료
1 |
|
컨테이너 초기화
1 | AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ConfigurationUserBean.class); |
AppliactionContext 객체를 생성하면 스프링 컨테이너를 초기화한다.
이 과정에서 메타데이터를 읽어서 알맞은 빈 객체를 생성하고 각 빈을 연결(주입)하게 된다.
컨테이너 종료
1 | applicationContext.close(); |
컨테이너를 close 메서드를 통해 종료하면 컨테이너가 관리하던 빈 객체들도 모두 소멸된다.
(이때 프로퍼티 스코프인 객체들은 소멸되지 않는다. )
빈 객체의 생명 주기
스프링 컨테이너의 생명주기를 보면 짐작할 수 있듯이 일반적인 빈 객체는 컨테이너의 통제에 따라 생명 주기를 진행한다.
- 객체 생성 -> ApplicationContext가 생성되면서 빈 객체 생성
- 의존 설정 -> ApplicationContext가 메타데이터를 기반으로 의존 주입
- 초기화 -> 의존관계가 모두 설정하고 나면 빈 객체의 초기화에 해당하는 메서드를 수행
- 소멸 -> ApplicationContext를 종료하면 빈 객체 소멸에 해당하는 메서드를 수행
초기화와 소멸
의존관계까지 모두 주입되고 나면 빈 객체가 등록될 때 어떤 행위를 하도록 하고싶거나,
특정 빈 객체가 소멸 될 때 어떤 행위를 하기 원할 때가 있다.
이런 의도를 빈 생명 주기 중 초기화와 소멸에서 수행할 수 있다.
초기화
빈 객체가 생성되고 나서 어떤 메서드를 실행시키고 싶은 경우 초기화 단계에서 수행한다.
이를 설정하기 위해서는 여러가지 방법이 있다.
- @PostConstruct
- InitializingBean 인터페이스
- init-method
코드로 이해해보자.
User 클래스가 빈 객체로 등록될 때 특정 메서드를 실행하게 만들자.
먼저 @PostConstruct
1 |
|
이렇게 빈 객체가 등록되고 나서 수행됐으면 하는 메서드에 @PostConstruct를 붙여주면 된다.
InitializingBean 인터페이스를 구현하는 방법도 있다.
1 |
|
afterPropertiesSet() 메서드 안에 빈 객체가 등록될 때 수행할 행동을 적어놓으면 된다.
마지막으로 xml init-method를 사용하는 방법이다.
1 | <bean id="user" class="nextstep.helloworld.core.User" init-method="setIsBeanInitialized"> |
Bean 태그 안에 init-method 속성에 빈 객체 생성 될 때 수행될 메서드 이름을 적어주면 된다.
소멸
빈 객체가 사라질 때 특정 행동을 하라고 설정할 수 있다.
소멸을 구현하는 데에도 여러 방법이 있다.
- @PreDestroy
- DisposableBean 인터페이스
- destroy-method
코드로 이해해보자.
User 클래스의 빈 객체가 폐기될 때 특정 메서드를 실행하게 만들자.
@PreDestory
1 |
|
DisposableBean 인터페이스
1 |
|
XML destroy-method
1 | <bean id="user" class="nextstep.helloworld.core.componentscan.user.User" destroy-method="setIsBeanInitialized"></bean> |
이렇게 하면 빈 객체가 제거 될 때(컨테이너가 종료될 때) 해당 메서드를 실행시킬 수 있다.