ApplicationContext는 IoC Conatiner라고 불리며, 스프링 애플리케이션에서 빈을 관리하는 기능을 포함해 다양한 기능을 제공하는, 애플리케이션 구성을 제공하는 중앙 인터페이스(Central interface) 입니다.
다음과 같은 인터페이스를 상속하고 있습니다.
ApplicationContext는 다양한 인터페이스를 구현하고 있어, 다양한 기능을 구현할 수 있습니다.
다만, AnnotationConfigServletWebServerApplicationContext를 보면 각 기능에 해당하는 필드를 가지고 있는 것을 확인할 수 있습니다.
ApplicationContext가 인터페이스를 구현하고 있기는 하지만 이 기능을 구현하는데 내부 필드로 사용하고 있다는 것입니다.
AbstractApplicationContext에서 BeanFactory를 조회할 때, 내부적으로 구현한 것이 아닌 getBeanFactory()를 호출하고 있음을 확인할 수 있습니다.
getBeanFactory()는 이처럼 추상 메서드로 정의되어 있습니다.
주석을 통해 해당 클래스를 확장한 자식 클래스는 BeanFactory를 해당 메서드를 통해 반환해야 함을 명시하고 있습니다.
AbstractApplicationContext의 자식 클래스 GenericApplicationContext에는 필드에 등록된 beanFactory를 반환하고 있음을 확인할 수 있습니다.
실제로 ApplicationContext refresh 과정 중 빈을 관리하는데 GenericApplicationContext.beanFactory가 사용되고 있음을 확인할 수 있습니다.
즉, ApplicationContext는 자기 자신이 BeanFactory를 확장하고 있지만, 실제로 빈을 관리하는 것은 내부 BeanFactory라는 것입니다.
AbstractApplicationContext를 확장한, 인스턴스 생성이 가능한 최초의 ApplicationContext는 GenericApplicationContext입니다.
실제 스프링 애플리케이션에서 사용되는 AnnotationConfigServletWebServerApplicationContext 등이 GenericApplicationContext를 모두 구현하고 있기 때문에 사실상 스프링에서는 ApplicationContext 자체가 빈을 관리하는 경우는 없다고 볼 수 있습니다.
즉 BeanFactory를 상속(구현)했지만 실질적으로는 조합을 사용하고 있다는 것입니다.
이는 스프링의 설계 철학을 통해 다음과 같은 추측이 가능합니다.
ApplicationContext는 BeanFactory를 포함해 다양한 기능을 제공합니다.
이로 인해 ApplicationContext은 상당히 복잡한 내부 구조를 가지게 되었습니다.
이를 ApplicationContext 내부 BeanFactory에 위임해 기능의 분리를 유도했다고 볼 수 있습니다.
이렇게 빈을 관리하는 기능을 내부 BeanFactory로 분리해, 만약 빈을 관리하는 다른 방식이 필요하다면 ApplicationContext 전체를 다시 선언하는 것이 아닌 내부 BeanFactory만 변경하는 등 확장성 또한 고려했음을 추측할 수 있었습니다.
요약하자면, 너무 많은 기능을 제공해 내부 구조가 복잡해진 ApplicatonContext에서 빈을 관리하는 기능을 내부 BeanFactory로 분리해 관심사를 분리시켜 빈 관리의 복잡성을 추상화 시켰다고 볼 수 있습니다.
이를 통해 유지보수와 확장성을 개선시키고자 했다고 볼 수 있습니다.
이러한 ApplicationContext의 특징과 디자인 패턴 중 비슷한 요소를 살펴보고자 합니다.
프록시 패턴은 한 객체가 다른 객체의 인터페이스 역할을 수행하는 디자인 패턴입니다.
ApplicationContext는 클라이언트에게는 BeanFactory의 기능을 제공하는 인터페이스 역할을 하면서, 실제 작업은 내부의 BeanFactory에 위임합니다.
이를 통해, 추가적인 기능(빈의 라이프사이클 관리, 메시지 리소스 처리, 이벤트 발행 등)을 제공하면서도, 기본적인 빈 관리 기능은 BeanFactory가 담당하는 구조입니다.
이와 같은 특징이 프록시 패턴과 유사하다고 볼 수 있습니다.
컴포지트 패턴은 객체를 트리 구조로 구성하여 개별(Leaf) 객체와 복합(Composite) 객체를 클라이언트가 동일하게 다룰 수 있도록 하는 디자인 패턴입니다.
빈을 관리한다는 기능을 기준으로 보자면, BeanFactory도 관리가 가능하지만 BeanFactory를 포함해 여러 인터페이스를 확장해 기능을 제공하는 ApplicationContext로도 빈을 관리할 수 있습니다.
이철머 ApplicationContext와 BeanFactory의 관계에서 볼 때, ApplicationContext는 복합 역할을 한다고 볼 수 있습니다.
이와 같은 특징이 컴포지트 패턴의 특징과 일부 일치하지만, ApplicationContext와 ApplicationContext가 확장하고 있는 인터페이스의 관계를 컴포지트 패턴이라고 표현하기에는 관점이 다르다고 생각됩니다.
BeanFactory는 기초적인 IoC Conatiner로 다음과 같은 기능을 제공합니다.
이러한 특징으로 인해 IoC Container라고 불립니다.
ApplicationContext는 ListableBeanFactory와 HierarchicalBeanFactory를 확장해 BeanFactory의 기능을 수행합니다.
각 인터페이스의 의미는 다음과 같습니다.
이를 통해 ApplicationContext는 단일 빈 조회 뿐만 아니라 빈의 목록을 조회할 수 있고, 여러 ApplicationContext를 계층 구조로 가질 수 있는 것을 확인할 수 있습니다.
MessageSource는 국제화(i18n) 기능을 제공하는 인터페이스입니다.
이를 활용해 다양한 언어 및 지역 설정에 따라 설정한 메세지를 해석하는 메세지 변환 기능을 제공합니다.
MessageSource의 경우 사용자가 별도로 설정했다면 그 MessageSource를 사용하며, 아닐 경우 DelegatingMessageSource를 사용합니다.
DelegatingMessageSource는 모든 메세지 분석을 부모 MessageSource에 위임하며, 부모 MessageSource가 없다면 별도로 동작하지 않습니다.
ResourceLoader는 파일 시스템이나 클래스 경로 등의 리소스를 로드하는 기능을 제공합니다.
이를 통해 텍스트 파일, 미디어 파일, 이미지 파일 등의 다양한 리소스를 파일 시스템, 클래스 경로, URL 등에서 일관된 방식으로 로드할 수 있습니다.
ApplicationContext는 ResourcePatternResolver를 확장해, ResourceLoader를 확장하고 있습니다.
ResourcePatternResolver는 위치 패턴(Ant 등)을 리소스로 해석하는 기능을 제공합니다.
ApplicationContext는 ResourcePatternResolver를 확장해 application.properties와 같은 설정 파일 리소스를 읽을 수 있습니다.
예를 들어, classpath:*.properties와 같이 리소스에 대한 패턴을 활용해 한 번에 여러 리소스를 읽을 수 있습니다.
ApplicationContext는 이러한 ResourcePatternResolver를 활용해 필요한 리소스를 패턴을 활용해 효율적으로 읽어옵니다.
ApplicationEventPublisher는 이벤트 발행 기능을 캡슐화한 함수형 인터페이스입니다.
이를 통해 ApplicationContext는 자체적으로 이벤트 발행 기능을 사용할 수 있습니다.
ApplicationContext refresh 수행 시 위와 같이 AbstractApplicationContext.finishRefresh()에서 ContextRefreshedEvent를 발행합니다.
protected void publishEvent(Object event, @Nullable ResolvableType typeHint) {
Assert.notNull(event, "Event must not be null");
ResolvableType eventType = null;
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent applEvent) {
applicationEvent = applEvent;
eventType = typeHint;
}
else {
ResolvableType payloadType = null;
if (typeHint != null && ApplicationEvent.class.isAssignableFrom(typeHint.toClass())) {
eventType = typeHint;
}
else {
payloadType = typeHint;
}
applicationEvent = new PayloadApplicationEvent<>(this, event, payloadType);
}
// Determine event type only once (for multicast and parent publish)
if (eventType == null) {
eventType = ResolvableType.forInstance(applicationEvent);
if (typeHint == null) {
typeHint = eventType;
}
}
// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext abstractApplicationContext) {
abstractApplicationContext.publishEvent(event, typeHint);
}
else {
this.parent.publishEvent(event);
}
}
}
메서드는 위와 같습니다.
이벤트를 발행하는 것은 위와 같은 코드로, ApplicationEventMulticaster에 이벤트를 전역적(= multicast)으로 발행하고 있음을 확인할 수 있습니다.
ApplicationEventMulticaster 내부에서 직접 이벤트를 발행하고 있음을 확인할 수 있습니다.
EnvironmentCapable은 환경 추상화(concrete environment)에 접근할 수 있는 기능을 제공하는 인터페이스입니다.
환경 정보를 얻을 수 있는 getEnvironment()를 제공합니다.
ApplicationContext refresh 진행 전 준비 과정에서 이전에 구한 Environment를 우선적으로 세팅해줍니다.
AbstractApplicationContext.getEnvironment()는 위와 같이 없으면 StandardEnvironment를 생성해 반환하고, 있으면 해당 Environment를 반환합니다.
이를 통해 ApplicationContext가 환경 설정 정보에 접근하고 관리할 수 있습니다.