ApplicationContext Refresh - refreshContext() 후반부

appti·2024년 3월 14일
0

분석

목록 보기
15/23

환경

버전은 스프링 부트 3.0.8이며, Spring MVC를 사용했습니다.

@SpringBootApplication이 명시된 클래스는 위와 같습니다.

서론

이전 글에서 createApplicationContext(), context.setApplicationStartup(), prepareContext(), refreshContext() - AbstractApplicationContext.refresh() 메서드의 try-catch 블록 밖에 존재하는 전반부까지 확인했습니다.

이번에는 AbstractApplicationContext.refresh() 메서드의 try-catch 블록 내부를 확인해보고자 합니다.

AbstractApplicationContext.refresh() - 후반부

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

		// Prepare this context for refreshing.
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);

			StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);
			beanPostProcess.end();

			// Initialize message source for this context.
			initMessageSource();

			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
			onRefresh();

			// Check for listener beans and register them.
			registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);

			// Last step: publish corresponding event.
			finishRefresh();
		}

		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}

			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();

			// Reset 'active' flag.
			cancelRefresh(ex);

			// Propagate exception to caller.
			throw ex;
		}

		finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
			contextRefresh.end();
		}
	}
}

위 코드가 AbstractApplicationContext.refresh() 메서드의 전체 코드입니다.

@Override
public void refresh() throws BeansException, IllegalStateException {
	...

	try {
		// Allows post-processing of the bean factory in context subclasses.
		postProcessBeanFactory(beanFactory);

		StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
		// Invoke factory processors registered as beans in the context.
		invokeBeanFactoryPostProcessors(beanFactory);

		// Register bean processors that intercept bean creation.
		registerBeanPostProcessors(beanFactory);
		beanPostProcess.end();

		// Initialize message source for this context.
		initMessageSource();

		// Initialize event multicaster for this context.
		initApplicationEventMulticaster();

		// Initialize other special beans in specific context subclasses.
		onRefresh();

		// Check for listener beans and register them.
		registerListeners();

		// Instantiate all remaining (non-lazy-init) singletons.
		finishBeanFactoryInitialization(beanFactory);

		// Last step: publish corresponding event.
		finishRefresh();
	}

	catch (BeansException ex) {
		...
	}

	finally {
		// Reset common introspection caches in Spring's core, since we
		// might not ever need metadata for singleton beans anymore...
		resetCommonCaches();
		contextRefresh.end();
	}
}

해당 영역의 코드만을 확인합니다.

postProcessBeanFactory()

AnnotationConfigServletWebServerApplicationContext.postProcessBeanFactory()가 호출됩니다.

super로 인해 ServletWebServerApplicationContext.postProcessBeanFactory()가 호출됩니다.

ServletContext와 관련된 설정을 BeanFactory에 적용합니다.
이후 ServletWebServerApplicationContext.registerWebApplicationScpes()를 호출합니다.

메서드 이름 그대로 @RequestScope 등과 같이, 별도로 Scope 설정을 적용한 빈을 등록합니다.
이 과정에서 각 Scope에 필요한 HttpSession과 같은 의존성도 BeanFactory에 적용합니다.

아직 ComponentScan이 수행되지 않은 시점이기 때문에 basePackages나 annotatedClasses 등의 정보가 수집되지 않은 상황이므로 두 분기문은 동작하지 않습니다.

this.applicationStartup.start("spring.context.beans.post-process")

모니터링 용도의 ApplicationStartup을 가져옵니다.

invokeBeanFactoryPostProcessors()

이전 단계에서 등록한 BeanPostProcessor를 실행시킵니다.
해당 메서드에서 빈으로 등록할 클래스의 BeanDefinition을 탐색합니다.

싱글톤 타입의 빈을 만들기 전 반드시 호출해야하는 AbstractApplicationContext.invokeBeanFactoryPostProcessors() 메서드입니다.

AbstractApplicationContext에서 발생하는 후처리 작업을 위임(Delegate)받아 수행하는 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors() 메서드를 호출합니다.

이 때 호출되는 getBeanFactoryPostProcessors()의 결과로는 위와 같이 3개가 존재합니다.

위의 두 개는 prepareContext().applyInitializers()에서 세팅된 값입니다.

나머지 하나는 ApplicationContext.prepareContext()에서 세팅된 값입니다.

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors() 메서드 내부에는 위와 같이 BeanDefinition을 등록하는 것을 확인할 수 있습니다.

위 코드는 SharedMetadataReaderFactoryContextInitializer 내부 코드입니다.

실행 결과 BeanDefinition이 하나 추가된 것을 확인할 수 있으며, 기존 8개의 BeanDefinition은 스프링 부트 내부적으로 생성한 BeanDefinition임에 비해 추가된 BeanDefinition은 개발자가 직접 만든 @SpringBootApplication임을 확인할 수 있습니다.

이후 ProiortyOrdered를 구현한 BeanDefinitionRegistryPostProcessor를 찾아 별도의 컬렉션 currentRegistryProcessors에 추가하고, 빈 이름 또한 processBeans에 저장합니다.

이후 위와 같은 과정을 통해 등록합니다.

  • sortPostpRocessors()
    • currentRegistryProcessors를 특정 조건(PriorityOrdered 및 Ordered 등) 기준으로 정렬합니다.
  • registryProcessors.addAll()
    • 작업을 마친 BeanDefinitionRegistryPostProcessor을 저장합니다.
  • currentRegistryProcessors.clear()
    • currentRegistryProcessors를 초기화합니다.

위와 같이 세 가지 메서드는 단순하지만 invokeBeanDefinitionRegistryPostProcessors()는 복잡하며, 4개의 메서드 중 핵심입니다.

현재 PriorityOrdered를 구현한 BeanDefinitionRegistryPostProcessor는 ConfigurationClassPostProcessor 하나이므로, 이 PostProcessor만 전달됩니다.

추가한 모든 BeanDefinitionRegistryPostProcessor를 순회하며 postProcessBeanDefinitionRegistry()를 호출합니다.

ConfigurationClassPostProcessor는 @Configuration, @Bean, @ComponentScan 등을 처리하며 BeanDefinition을 조회합니다.

그러므로 해당 코드에서 @ComponentScan의 기준이 되는 basepackage를 찾기 위해 @SpringBootApplication이 붙은 클래스를 찾아서 configCandidates에 저장합니다.

해당 @SpringBootApplication을 ConfigurationClassParser를 통해 파싱을 수행합니다.

이후 @SpringBootApplication부터 시작해 doProcessConfigurationClass()를 재귀적으로 호출하며, 지정한 클래스의 계층 구조를 확인하며 추가적으로 설정해야 하는 부분이 있는지 확인합니다.

@SpringBootApplication이므로, doProcessConfigurationClass() 내부에서 ComponentScan을 수행합니다.

즉, @SpringBootApplication은 자기 자신만 빈으로 등록하면 되는 것이 아닌, 내부적으로 @ComponentScan을 수행하므로 추가적인 설정이 필요하다는 의미입니다.

이를 확인하기 위해 ComponentScan을 수행합니다.

ComponentScanAnnotationParser.parse() 내부에서는 ComponentScan에 필요한 다양한 속성(excludeFilters, includeFilters 등)을 설정합니다.

위 코드에서 basePackages를 설정하는 것을 확인할 수 있습니다.

이후 ClassPathBeanDefinitionScanner를 통해 basePackages 기반으로 스캔을 진행합니다.

ComponentScan의 후보가 되는, @Component가 붙은 후보군들을 findCandidateComponents()를 통해 조회합니다.

findCandidateComponents()는 내부적으로 basePackage를 기준으로 모든 파일을 조회한 뒤, 조건에 맞으면 후보군에 추가합니다.

basePackage를 기준으로 DTO를 포함한 모든 클래스 파일을 조회하고 있음을 확인할 수 있습니다.

조건에 따라 BeanDefinition이 candidates에 추가된 것을 확인할 수 있습니다.

이렇게 조회한 candidates 중 등록되지 않은 후보만 추가해 반환합니다.

이 후보들은 ComponentScan 과정을 통해 조회한 것이므로, 이 후보들을 탐색하며 조건에 맞는 경우 다시 parse()를 호출합니다.

다시 doProcessConfiguartionClass()를 재귀적으로 호출합니다.
디버깅으로 확인해보면 @SpringBootApplication이 아닌, ComponentScan을 통해 얻은 후보군들 중 하나임을 확인할 수 있습니다.

후보군이었던 AuctionService는 단순히 @Service가 붙은 클래스이므로, 추가적으로 설정할 부분이 없어 sourceClass가 null이 되어 do-while문을 빠져나왔음을 확인할 수 있습니다.

이렇게 설정이 끝난 클래스를 configuarionClasses에 추가합니다.

해당 단계에서 237개의 빈 후보군을 찾은 것을 확인할 수 있습니다.

이후 빈 후보군을 BeanDefinitionRegistry에 등록하고, 중복된 값을 방지하기 위해 등록한 클래스들을 alreadyParsed 컬렉션에 저장합니다.

ComponentScan을 통해 533개의 BeanDefinition을 조회했습니다.

이후 유사한 작업을 수행해 BeanPostProcessor를 동작시킵니다.
이걸로 invokeBeanFactoryPostProcessors()의 동작이 끝납니다.

registerBeanPostProcessors()

BeanPostProcessor를 등록합니다.

PostProcessorRegistrationDelegate.registerBeanPostProcessors()를 호출합니다.

BeanFactory에 등록된 모든 BeanDefinition 중 지정한 타입인 BeanPostProcessor만을 조회합니다.

이후 BeanPostProcessor를 로깅하는 용도인 BeanPostProcessorChecker를 등록합니다.

beanProcessorTargetCount는 이전에 BeanFactory에 추가된 BeanPostProcessor + BeanPostProcessorChecker 자기 자신 개수 + BeanFactory에 추가할 BeanPostProcessor 개수로 지정됩니다.

조회한 postProcessorNames 중 PriorityOrdered, Ordered, 내부 구현, 아무 조건도 없는 BeanPostProcessors를 별도의 컬렉션으로 분리합니다.

BeanFactoryPostProcessor와 유사하게 BeanPostProcessor를 정렬하고 등록합니다.

첫 번째로 PriorityOrdered를 구현한 BeanPostProcessor 입니다.

만약 BeanFactory가 AbstractBeanFactory를 확장하고 있다면 addBeanPostProcessors()를 활용해 내부적으로 CopyOrWriteArrayList로 효율적으로 추가하고, 그렇지 않다면 BeanPostProcessor를 순회하며 하나씩 추가합니다.

Ordered를 구현한 BeanPostProcessor를 등록합니다.

일반적인 BeanPostProcessor를 등록합니다.

특별한(= internal) BeanPostProcessor를 다시 등록합니다.
다시 등록하는 이유는 BeanPostProcessor의 경우 엄격한 순서로 실행되어야 하기 때문에, 이미 등록한 BeanPostProcessor를 다시 등록해 순서를 정렬하는 것입니다.

ApplicationListenerDetector까지 다시 등록합니다.

beanPostProcess.end()는 별도의 동작을 하지 않습니다.

initMessageSource()

MessageSource를 초기화합니다.

만약 개발자가 별도로 MessageSource를 설정했고, 그 MessageSource가 HierarchicalMessageSource이면서 부모 MessageSource가 설정되지 않은 경우, 개발자가 등록한 MessageSource가 해석할 수 없는 부분을 부모가 해석할 수 있도록 설정합니다.

개발자가 별도로 MessageSource를 설정하지 않았다면 DelegatingMessageSource를 빈으로 등록합니다.

initApplicationEventMulticaster()

ApplicationEvent를 발행할 Multicaster를 초기화합니다.

이미 ApplicationEventMulticaster가 빈으로 등록되어있다면 ApplicationContext의 필드로 초기화합니다.

ApplicationEventMulticaster가 빈으로 등록되어 있지 않다면 SimpleApplicationEventMulticaster를 빈으로 등록합니다.

onRefresh()

refresh 과정을 위해 필요한 다른 빈들을 등록합니다.

super.onRefresh()로 인해 GenericWebApplicationContext.onRefresh()가 호출됩니다.

GenericWebApplicationContext.onRefresh()는 Ui 관련 테마를 설정합니다.
대부분 스프링 부트를 활용한 서버는 SSR을 사용하지 않고 REST API를 사용하는 경우가 많으므로 크게 중요한 부분은 아닙니다.

WebServer, 스프링 부트에 내장되어 있는 Embedded Tomcat을 생성하고 실행합니다.

빈으로 등록된 WebServerFactory를 찾아 WebServer를 가져오게 됩니다.

getWebServerFactory()는 Auto Configuration으로 인해 등록된 ServletWebServerFactory 타입의 빈을 조회해 반환합니다.

Spring MVC이기 때문에 TomcatServletWebServerFactory가 반환됩니다.

registerListeners()

ApplicationListener를 등록하고 이전에 발행된 ApplicationEvent를 발행합니다.

ApplicationListener가 ApplicationEvent를 발행받기 위해서는ApplicationEventMulticaster에 등록되어야 합니다.
ApplicationContext에 등록된 ApplicationListener를 ApplicationEventMulticaster에 등록합니다.

BeanFactory에 등록된 ApplicationListener를 ApplicationEventMulticaster에 등록합니다.

이 때 빈 이름만을 저장하는데, 그 이유는 추후 진행할 단계에서 BeanPostProcessor가 빈으로 등록한 ApplicationListener를 주입하기 때문입니다.

이후 ApplicationContext 과정에서 처리할 수 없었던, 미리 발행한 ApplicationEvent(= earlyApplicationEvents)를 발행합니다.

이는 개발자가 임의로 ApplicationEvent를 발행하지 않는다면 발생하지 않습니다.

finishBeanFactoryInitialization()

BeanFactory에 설정한 정보를 토대로 빈을 등록합니다.

ConversionService가 BeanFactory에 등록되어 있다면 BeanFactory 필드로 초기화하는 로직입니다.
prepareContext()에서 ApplicationContext에 이미 등록했기 때문에 동작하지 않습니다.

빈의 속성 값에서 Placeholder를 분석하는 EmbeddedValueResolver가 BeanFactory에 없다면 등록하는 로직입니다.

기본적으로 PropertySourcesPlaceholderConfigurer를 추가했기 때문에 동작하지 않습니다.

LocalTimeWeaverAware를 사용하는 빈 이름이 있다면 해당 빈을 등록합니다.
현재 JPA를 의존하고 있기 때문에 EntityManagerFactory가 존재하므로 이 로직이 수행됩니다.

BeanFactory에 필요한 설정을 모두 마쳤기 때문에, 클래스 로더를 초기화해준 뒤 freezeConfiguration()를 호출해 BeanFactory의 설정이 변경되지 않도록 합니다.

beanFactory.preInstantiateSingletons() 메서드를 통해 지연 초기화가 아닌 모든 싱글톤 빈들을 등록합니다.

preInstantiateSingletons()에서는 BeanFactory가 가지고 있는 모든 BeanDefinitionNames를 순회하며 getBean()을 호출합니다.

getBean()은 doGetBean()을 호출하며, 만약 getBean() 시 BeanFactory에 빈이 등록되지 않은 경우 해당 빈의 Scope에 따라 빈을 생성합니다.

이 때 사용하는 BeanFactory는 AbstractAutowireCapableBeanFactory입니다.

빈 생성 시 Autowired로 인해 다른 빈들의 의존 관계 주입이 필요하지 않은 빈들을 우선적으로 등록한 뒤, 의존 관계 주입이 필요한 빈들을 등록합니다.

최종적으로 ConstructorResolver에서 찾은 생성자와 의존 관계 주입 시 필요한 파라미터를 찾아, BeanUtils.instantiateClass()에 전달해 인스턴스를 생성합니다.

이 모든 작업이 끝나면 캐시를 삭제하고 ApplicationStartup 모니터링을 위해 Refresh 작업이 끝났음을 표현합니다.

별도의 설정을 하지 않은 경우 특정 동작을 수행하지는 않습니다.

정리

BeanFactory에서 BeanFactoryPostProcessor와 BeanPostProcessor를 활용해 빈을 등록합니다.

profile
안녕하세요

0개의 댓글