일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- map
- aws secretmanager
- traceasynccustomautoconfiguration
- EnableWebMvc
- b3-propagation
- java.util.list
- jpa
- java lambda
- asynccustomautoconfiguration
- micrometer tracing
- spring MVC
- ResponseBody
- Sleuth
- Spring Boot
- CompletableFuture
- SpringMVC
- java
- elasticsearch
- list
- spring3 spring2 traceid
- spring
- java list
- asyncconfigurer
- awssecretsmanagerpropertysources
- HashMap
- Spring JPA
- DeferredImportSelector
- @FunctionalInterface
- kotlin
- traceId
- Today
- Total
du.study기록공간
Spring boot Auto Configuration 동작방식 본문
이전에도 간략하게나마 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 를 직접 구현하여 빈을 등록하는 경우는 다른 포스팅을 통하여 다시 작성할 예정입니다.
'스프링' 카테고리의 다른 글
Spring Webflux + grpc + Armeria (1) | 2021.02.07 |
---|---|
Spring boot AutoConfiguration 동작방식2 - DeferredImportSelector (0) | 2021.01.10 |
Spring Scheduling @EnableScheduling, @Scheduled (0) | 2020.05.27 |
Spring boot Web embedded Container(server) 변경 (0) | 2020.05.10 |
Spring boot @SpringBootApplication (0) | 2020.05.07 |