
Spring Container에 대해 공부하다 보면
계속해서 등장하는 두 가지 이름이 있다.
BeanFactory
ApplicationContext
처음에는 이런 생각이 들었다.
둘 다 Spring Bean을 관리하는 것 같은데 대체 무슨 차이지?
지금까지 내가 작성했던 코드에서는 대부분 다음과 같이
ApplicationContext를 사용하고 있었다.
ApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
그리고 Bean을 가져올 때는
MemberService memberService =
ac.getBean("memberService", MemberService.class);
와 같이 사용했다.
그렇다면 BeanFactory는 무엇이고,
우리가 지금까지 사용하던 ApplicationContext와는 어떤 차이가 있을까?
이번 글에서는
BeanFactory
ApplicationContext
ApplicationContext가 제공하는 부가기능
Spring Container가 다양한 설정 형식을 지원하는 방법
까지 정리해보고자 한다.
BeanDefinition에 대한 내용은 다음 글에서 이어서 다룰 예정이다.
앞에서 Spring Bean을 공부하면서
Spring Container에 대해서도 간단하게 살펴봤다.
예를 들어
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
라는 코드를 작성하면
AppConfig.class
↓
Spring Container 생성
↓
설정 정보 확인
↓
Spring Bean 등록
↓
Bean 간 의존관계 설정
과 같은 과정이 진행된다.
쉽게 말하면 Spring Container는
애플리케이션에서 사용할 객체들을 생성하고 관리하며, 객체 사이의 관계까지 관리하는 공간
이라고 생각할 수 있다.
그런데 조금 더 정확하게 들어가면
Spring Container와 관련된 대표적인 인터페이스가 두 개 등장한다.
BeanFactory
ApplicationContext
먼저 BeanFactory부터 살펴보자.
BeanFactory는
Spring Container의 최상위 인터페이스
라고 할 수 있다.
Spring Bean을 관리하고 조회하는 가장 기본적인 기능을 제공한다.
예를 들어 지금까지 계속 사용했던
getBean()
도 BeanFactory에서 제공하는 대표적인 기능이다.
MemberService memberService =
ac.getBean(MemberService.class);
이 코드에서 수행하는
Spring Container에서
MemberService 타입의 Bean을 찾아서
객체를 반환
하는 기능의 핵심이 BeanFactory에 정의되어 있는 것이다.
즉 BeanFactory의 가장 중요한 역할을 간단하게 정리하면
Spring Bean 관리
Spring Bean 조회
라고 할 수 있다.
이름을 그대로 보면 조금 더 이해하기 쉽다.
Bean + Factory
즉
Bean을 만들고 관리하는 Factory
라고 생각할 수 있다.
Spring Container 내부에 다음 Bean들이 있다고 해보자.
Spring Container
│
├── memberService
├── memberRepository
├── orderService
└── discountPolicy
BeanFactory는 이런 Bean들을 관리하고
getBean()
을 통해 필요한 Bean을 찾을 수 있도록 해주는 핵심 인터페이스다.
그래서 지금까지 우리가 Spring Bean을 조회하면서 사용했던
많은 기능들은 사실 BeanFactory가 제공하는 기능이었다.
이제 우리가 실제 코드에서 더 자주 봤던
ApplicationContext를 살펴보자.
ApplicationContext는 BeanFactory의 기능을 이어받는다.
구조를 아주 단순하게 표현하면 다음과 같다.
BeanFactory
↑
│
ApplicationContext
즉,
BeanFactory가 할 수 있는 일
Spring Bean 관리
Spring Bean 조회
getBean()
...
을 ApplicationContext도 할 수 있다.
그래서 다음 코드가 가능했던 것이다.
ApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService =
ac.getBean(MemberService.class);
ApplicationContext 역시
Bean을 조회할 수 있다.
여기서 자연스럽게 이런 의문이 생겼다.
Bean 관리와 조회를 BeanFactory가 이미 해주는데
ApplicationContext는 왜 따로 존재하지?
애플리케이션을 실제로 개발하다 보면
객체를 만들고 찾는 기능만으로는 부족하기 때문이다.
예를 들어 실제 서비스를 만든다고 생각해보자.
단순히
객체 생성
객체 조회
만 필요한 것이 아니다.
개발 환경과 운영 환경을 구분하고 싶다.
사용자의 언어에 따라 다른 메시지를 보여주고 싶다.
특정 이벤트가 발생했을 때 다른 객체에게 알리고 싶다.
파일이나 외부 Resource를 읽고 싶다.
같은 기능도 필요하다.
그래서 ApplicationContext는
BeanFactory의 Bean 관리 기능
+
애플리케이션 개발에 필요한 여러 부가기능
을 제공한다.
강의에서는 대표적으로 다음 네 가지를 다뤘다.
메시지 소스를 활용한 국제화
환경 변수
애플리케이션 이벤트
편리한 Resource 조회
하나씩 살펴보자.
첫 번째는 국제화 기능이다.
예를 들어 하나의 서비스를
한국과 미국에서 제공한다고 생각해보자.
한국 사용자에게는
안녕하세요.
라고 보여주고 싶고,
영어권 사용자에게는
Hello.
라고 보여주고 싶다.
물론 직접 코드로
if (country.equals("KR")) {
return "안녕하세요.";
}
if (country.equals("US")) {
return "Hello.";
}
와 같이 작성할 수도 있다.
하지만 언어가 많아질수록 관리하기 어려워진다.
Spring에서는 MessageSource와 관련된 기능을 이용해
이러한 국제화 기능을 지원할 수 있다.
쉽게 생각하면
한국 사용자
↓
한국어 Message
미국 사용자
↓
영어 Message
처럼 상황에 맞는 메시지를 가져올 수 있게 해주는 것이다.
두 번째는 환경 정보 관리다.
애플리케이션을 개발하다 보면
같은 프로그램이라도 실행되는 환경에 따라 설정값이 달라질 수 있다.
예를 들어 데이터베이스를 생각해보자.
내 컴퓨터에서 개발할 때
localhost DB
를 사용하다가
운영 서버
실제 운영 DB
를 사용해야 할 수도 있다.
즉 같은 애플리케이션이라도
Local
Development
Test
Production
환경에 따라 다른 설정을 사용할 필요가 있다.
ApplicationContext는 이런 환경 정보를 다룰 수 있는 기능을 제공한다.
Spring을 공부하다 보면 나중에
Profile
Environment
application.properties
application.yml
같은 개념들을 만나게 되는데
이 부분과도 연결되는 내용이라고 볼 수 있다.
세 번째는 Application Event다.
애플리케이션 내부에서 어떤 일이 발생했을 때
다른 객체에게 이를 알려야 하는 상황이 있을 수 있다.
예를 들어 주문이 완료됐다고 해보자.
주문 완료
라는 하나의 사건이 발생하면
재고 감소
사용자 알림 전송
주문 기록 저장
통계 데이터 업데이트
같은 여러 작업이 필요할 수도 있다.
물론 주문 코드 안에서 모든 객체를 직접 호출할 수도 있다.
하지만 이벤트 방식을 사용하면
주문 완료
↓
Event 발행
↓
┌─────────────┬─────────────┬─────────────┐
↓ ↓ ↓
재고 처리 알림 처리 통계 처리
처럼 이벤트를 발행하고 구독하는 방식으로 설계할 수 있다.
ApplicationContext는 이러한 애플리케이션 이벤트 기능을 편리하게 지원한다.
마지막은 Resource 조회 기능이다.
애플리케이션을 개발하다 보면
다양한 위치의 파일이나 데이터를 읽어야 할 수 있다.
예를 들어
파일 시스템의 파일
classpath 내부 파일
외부 URL의 Resource
등이 있다.
ApplicationContext는 이런 Resource들을
일관된 방식으로 조회할 수 있도록 도와준다.
쉽게 말하면
"이 파일이 classpath에 있나?"
"외부 파일인가?"
"URL Resource인가?"
를 각각 완전히 다른 방식으로 처리하기보다는
Spring의 Resource 기능을 통해 편리하게 접근할 수 있도록 하는 것이다.
조금 더 정확하게 보면
ApplicationContext는 단순히 BeanFactory 하나만 가지고 있는 것이 아니다.
여러 기능을 제공하는 인터페이스들의 역할을 함께 가진다.
개념적으로 보면 다음과 같다.
BeanFactory
↑
│
MessageSource ← ApplicationContext → ResourceLoader
│
│
Environment 관련 기능
│
│
Application Event
그래서 ApplicationContext 하나를 사용하면
Bean 관리
Bean 조회
국제화
환경 정보
이벤트
Resource 조회
같은 기능들을 함께 사용할 수 있다.
이제 둘의 차이를 비교해보면 생각보다 단순하다.
| 구분 | BeanFactory | ApplicationContext |
|---|---|---|
| Spring Bean 관리 | O | O |
| Spring Bean 조회 | O | O |
getBean() | O | O |
| 국제화 | 기본 핵심 기능 아님 | O |
| 환경 정보 | 기본 핵심 기능 아님 | O |
| Application Event | 기본 핵심 기능 아님 | O |
| Resource 조회 | 기본 핵심 기능 아님 | O |
쉽게 표현하면
BeanFactory
Bean 관리 + Bean 조회
이고,
ApplicationContext
BeanFactory 기능
+
애플리케이션 개발에 필요한 여러 부가기능
이라고 생각할 수 있다.
실제로 Spring 애플리케이션을 개발하면서
BeanFactory를 직접 사용할 일은 거의 없다고 한다.
대부분은 ApplicationContext를 사용한다.
왜냐하면
Bean 관리
+
환경 설정
+
이벤트
+
Resource
+
국제화
같은 실제 애플리케이션 개발에 필요한 기능들을 함께 제공하기 때문이다.
그래서 지금까지 우리가 사용했던 코드도
ApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
였다.
또는 구체 구현체를 직접 사용해서
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
라고 작성하기도 했다.
여기서 이름이 하나 더 등장한다.
AnnotationConfigApplicationContext
처음 보면 이름부터 상당히 길다...
하지만 구조를 보면 어렵지 않다.
BeanFactory
↑
ApplicationContext
↑
AnnotationConfigApplicationContext
BeanFactory와 ApplicationContext는 인터페이스이고,
AnnotationConfigApplicationContext는
애노테이션 기반의 Java 설정을 사용할 수 있는
ApplicationContext 구현체 중 하나라고 이해하면 된다.
그래서
new AnnotationConfigApplicationContext(AppConfig.class);
라고 작성하면
AppConfig.class
↓
Java 설정 정보 읽기
↓
Spring Container 생성
↓
Spring Bean 등록
이 진행된다.
여기까지 공부하면 용어가 조금 헷갈릴 수 있다.
BeanFactory
ApplicationContext
Spring Container
의 관계가 무엇일까?
강의에서는
BeanFactory나 ApplicationContext를 Spring Container라고 한다.
라고 정리한다.
다만 실제 개발에서는
BeanFactory를 직접 사용하는 경우가 거의 없기 때문에
ApplicationContext
=
Spring Container
처럼 이야기하는 경우가 많다.
그래서 앞으로 Spring Container라는 표현이 나오면
대부분 ApplicationContext를 생각해도 크게 문제없을 것 같다.
지금까지 내용을 한 번 정리해보자.
BeanFactory
↓
Spring Container의 최상위 인터페이스
↓
Bean 관리
Bean 조회
getBean()
그리고
ApplicationContext
↓
BeanFactory의 기능을 이어받음
↓
Bean 관리
Bean 조회
+
국제화
환경 정보
Application Event
Resource 조회
결론적으로
ApplicationContext는 BeanFactory의 Bean 관리 기능에 애플리케이션 개발에 필요한 여러 부가기능을 더한 인터페이스라고 볼 수 있다.
그리고 실무에서는 대부분
ApplicationContext
를 사용한다.
여기서 또 하나 재미있는 특징이 있다.
Spring Container는
설정 정보를 반드시 Java 코드로만 받아야 하는 것이 아니다.
지금까지는 다음 방식만 사용했다.
new AnnotationConfigApplicationContext(AppConfig.class);
그리고 AppConfig는
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
처럼 Java 코드로 작성되어 있었다.
그런데 Spring은 설정 형식에 상당히 유연하게 설계되어 있다.
강의에서는 대표적으로
Java Code
XML
Groovy
기타 형식
등의 설정 정보를 사용할 수 있다고 설명한다.
우리가 지금까지 사용했던 것이 바로
애노테이션 기반 Java 설정이다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
}
그리고 이 설정을
new AnnotationConfigApplicationContext(AppConfig.class);
에 넘긴다.
AppConfig.class
↓
AnnotationConfigApplicationContext
↓
Spring Container
지금 Spring을 공부하면서 가장 자주 보게 되는 방식이기도 하다.
Spring에서는 XML을 이용해서도
Bean 설정 정보를 작성할 수 있다.
예를 들어 Java에서
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
라고 작성했던 것을 XML에서는 대략 다음처럼 표현할 수 있다.
<bean id="memberRepository"
class="hello.core.member.MemoryMemberRepository" />
또 의존관계 역시 XML로 정의할 수 있다.
<bean id="memberService"
class="hello.core.member.MemberServiceImpl">
<constructor-arg
name="memberRepository"
ref="memberRepository" />
</bean>
결국 Java 설정에서 했던
new MemberServiceImpl(memberRepository());
라는 객체 구성 정보를
XML로 표현한 것이라고 볼 수 있다.
Java 설정에서는
AnnotationConfigApplicationContext
를 사용했다.
XML 설정에서는
GenericXmlApplicationContext
를 사용할 수 있다.
예를 들어
ApplicationContext ac =
new GenericXmlApplicationContext("appConfig.xml");
처럼 XML 설정 파일을 넘길 수 있다.
그리고 이후 Bean을 조회하는 방법은 똑같다.
MemberService memberService =
ac.getBean("memberService", MemberService.class);
여기서 꽤 중요한 점이 보인다.
설정 방식이
Java
이든
XML
이든
우리가 Bean을 사용하는 방식은 거의 달라지지 않는다.
ac.getBean(...)
을 그대로 사용할 수 있다.
최근 Spring Boot 기반 프로젝트에서는
XML 설정을 예전만큼 많이 사용하지 않는다.
대부분
@Configuration
@Bean
@Component
@Service
@Repository
@RestController
같은 Java와 Annotation 기반의 설정을 많이 사용한다.
그래서 처음 Spring을 배우는 입장에서는
"XML은 안 배우면 되는 것 아닌가?"
라는 생각도 들었다.
하지만 오래된 Spring 프로젝트, 즉 Legacy Project에서는
아직 XML 기반의 설정을 만날 수 있다.
또 XML은 Java 코드를 다시 컴파일하지 않고도
설정 정보를 변경할 수 있다는 특징도 있다.
따라서
XML을 주력으로 사용해야 한다.
기보다는
Spring은 XML로도 설정할 수 있고, 레거시 프로젝트에서는 실제로 만날 수도 있다.
정도로 이해하고 넘어가면 될 것 같다.
그런데 생각해보면 조금 이상하다.
Java에서는
@Configuration
@Bean
으로 설정한다.
XML에서는
<bean>
으로 설정한다.
형식이 완전히 다르다.
그런데 Spring Container에서는 결국 둘 다
Spring Bean
으로 똑같이 관리한다.
구조를 보면
AppConfig.class
↓
AnnotationConfigApplicationContext
↓
ApplicationContext
또는
appConfig.xml
↓
GenericXmlApplicationContext
↓
ApplicationContext
가 된다.
그렇다면 이런 의문이 생긴다.
Spring은 Java 코드와 XML처럼 완전히 다른 형식의 설정 정보를 어떻게 똑같이 처리할 수 있는 걸까?
개인적으로 이 부분이 꽤 흥미로웠다.
Spring Container 입장에서는
Java 설정인지
XML 설정인지
Groovy 설정인지
를 각각 전부 다르게 처리한다면
설정 형식이 하나 추가될 때마다 Container 자체도 계속 변경되어야 할 것 같다.
그런데 Spring은 이 문제를
하나의 추상화를 통해 해결한다.
그 중심에 있는 것이 바로
BeanDefinition
이다.
이번 글에서는 여기까지만 정리하고자 한다.
이번에 공부한 내용을 가장 짧게 정리하면
BeanFactory
→ Spring Bean 관리와 조회의 핵심 기능
ApplicationContext
→ BeanFactory 기능
+ 실제 애플리케이션에 필요한 다양한 부가기능
이라고 할 수 있다.
그리고
BeanFactory
ApplicationContext
둘 모두 Spring Container라고 부를 수 있지만,
실제 개발에서는 대부분 ApplicationContext를 사용한다고 보면 된다.
또 하나 중요한 점은
Spring Container는 특정 설정 형식 하나에 묶여 있지 않다.
는 것이다.
Java
XML
Groovy
...
와 같이 서로 다른 형태의 설정 정보를 받아들일 수 있도록
유연하게 설계되어 있다.
그런데 대체 어떻게 이런 것이 가능할까?
Java 설정
↘
???
↗
XML 설정
↓
Spring Container
여기에서 ???에 들어가는 핵심 개념이
바로 BeanDefinition이다.
다음 글에서는 Spring이 다양한 설정 정보를 어떻게 하나의 형태로 추상화하는지,
그리고 Spring Bean 설정 메타정보인 BeanDefinition이 무엇인지 정리해보고자 한다.