du.study기록공간

Spring boot Auto Configuration 동작방식 본문

스프링

Spring boot Auto Configuration 동작방식

du.study 2021. 1. 3. 16:40
728x90

 

이전에도 간략하게나마 Spring boot의 동작 원리를 작성한 적이 있습니다.

이번에는 그 과정을 좀 더 자세하게 기록해 보고자 합니다.

 

결론부터 말하면 스프링 부트는 두가지 방법으로 bean 등록을 하게됩니다.

- ComponentScan 을 통한 bean등록

- DeferredImportSelector 를 구현하고 있는 클레스를 @Import를 통하여 불러오는 @Configuration을 선언하고 있는 클레스

 

 

우선은 SpringBoot의 시작부분부터 시작해보겠습니다.

 

1. @SpringBootApplication

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@SpringBootConfiguration → @Configuration을 품은 어노테이션

@EnableAutoConfiguration → EnableAutoConfiguration을 통해 읽어온 빈을 등록하는 역할 수행

  • EnableAutoConfiguration 안에서는 AutoConfigurationImportSelector를 import하고있다.

여기서 AutoConfigurationImportSelector 는 DeferredImportSelector를 구현하고 있습니다.

 

 

2. SpringApplication.run 내부 분해해보기 ( SpringApplication)

 

    // 1)
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class[]{primarySource}, args);
    }
    
    
    // 2)
    public ConfigurableApplicationContext run(String... args) {
        ....
        ConfigurableApplicationContext context = null;
        ....

        try {
            ....
            context = this.createApplicationContext();
            ....
            this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            ....

1) 맨처음 SpringApplication.run 을 실행할때 인자값으로 넘긴 클레스 메타정보를primarySources 라는 변수에 저장.

2-1) createApplicationContext 을 통해 처음에 설정되는 WebApplicationType에 따라 별도의 ApplicationContext를 생성.

  • WebApplicationType 기본설정 : SpringApplication 생성자에서 WebApplicationType 내부 static함수인 deduceFromClasspath() 을 통해 타입 추론

  • WebApplicationType 설정 변경방법

- 코드

SpringApplication application = new SpringApplication(메인클레스.class);
application.setWebApplicationType(WebApplicationType.NONE);
application.run(args);

- 설정파일

spring:
  main:
    web-application-type: none

2-2) this.prepareContext 과정을 통해서 beanFactory 내부에 primarySource 메타정보 저장 (메인클레스 메타정보 저장)

2-3) this.refreshContext과정을 통하여 내부 AbstractApplicationContext() refresh() 함수 호출

 

 

 

- 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();
			}
            ....
		}
	}

 

3. invokeBeanFactoryPostProcessors

  • 각종 Configuration 정보를 호출하여 등록된 빈들을 인스턴스화 합니다.

  • 명시적인 순서를 지킨다.

  • @ComponentScan 을 좀 깊게 보고싶으시다면 해당 함수 내부동작을 확인하면 좋습니다.

  • DeferredImportSelector를 구현한 클레스의 process함수를 호출

 

 

이게 왜 중요한지는 아래 코드를 보면 알 수 있습니다.

EnableAutoConfiguration 에서 import하고있는 AutoConfigurationImportSelector 는 DeferredImportSelector를 구현하고있으며, 해당 부분을 통해, getAutoConfigurationEntry 이 호출되며, 이로인해 스프링 부트에서 AutoConfiguration인해 등록되는 빈 메타를 가져오게 됩니다.

// 1. AutoConfigurationImportSelector 는 DeferredImportSelector를 구현하고있다.
public class AutoConfigurationImportSelector implements DeferredImportSelector, ...


// 2. process를 통하여 EnableAutoConfiguration 메타 클레스 정보를 전부 호출한다.
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
  ....
  AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
      .getAutoConfigurationEntry(annotationMetadata);
  ....
}


protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  if (!isEnabled(annotationMetadata)) {
    return EMPTY_ENTRY;
  }
  ....
  List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // EnableAutoConfiguration.class으로 등록된 설정 메타를 가져온다 -> 2.2.5 기준 130개
  configurations = removeDuplicates(configurations);
  Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  checkExcludedClasses(configurations, exclusions);
  configurations.removeAll(exclusions);
  configurations = getConfigurationClassFilter().filter(configurations);
  fireAutoConfigurationImportEvents(configurations, exclusions);
  return new AutoConfigurationEntry(configurations, exclusions);
}
  • 설정파일의 위치 : spring-boot-autoconfigure 아래 META-INF/spring.factories

4. 그렇다면 자동설정된 configuration bean들은 전부 등록이 되는가? 에 대해선 선별과정을 거치게 됩니다.

선별과정인

해당 설정이 완료된 파일은 ConfigurationClassParser 클레스의 processConfigurationClass()함수 호출

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
  if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    return;
  }
  ....

해당 shouldSkip 과정에서 Conditional 어노테이션을 찾아 조건에 부합하는지 확인하고 해당 조건을 충족하는경우에만 등록진행을 하게됩니다. 

  • (아래사진)Conditional 어노테이션을 사용하고있는 세부 어노테이션.

 

 

결론

1. Spring boot를 이용하는 경우, WebApplicationType에 따라 자동으로 등록되는 빈들이 있으며, 

해당 빈들의 등록은 결국 DeferredImportSelector 를 구현하고 있는 클레스를 import하는 Configuration이 있는경우, 등록이 진행된다.

 

2. DeferredImportSelector 를 직접 구현하는경우, ComponentScan을 사용하지 않고도 bean을 등록할 수 있다.

 

 

 

DeferredImportSelector 를 직접 구현하여 빈을 등록하는 경우는 다른 포스팅을 통하여 다시 작성할 예정입니다.

 

728x90
Comments