728x90
스프링 부트의 동작
스프링 부트는 다음 경로에 있는 파일을 읽어서 스프링 부트 자동 구성으로 사용한다.
resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
스프링 부트가 제공하는 spring-boot-autoconfigure 라이브러리의 다음 파일을 확인해 보면 스프링 부트 자동 구성을 확인할 수 있다.
- spring-boot-autoconfigure - org.springframework.boot.autoconfigure.AutoConfiguration.imports
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
...
스프링 부트 자동 구성 동작 원리
- @SpringBootApplication -> @EnableAutoConfiguration -> @Import(AutoConfigurationImportSelector.class)
SpringBootAppliction
@SpringBootApplication
public class AutoConfigApplication {
public static void main(String[] args) {
SpringApplication.run(AutoConfigApplication.class, args);
}
}
- run() 에 보면 AutoConfigApplication.class 를 넘겨주는데, 이 클래스를 설정 정보로 사용한다는 뜻이다.
- AutoConfigApplication 에는 @SpringBootApplication 애노테이션이 있는데, 여기에 중요한 설정 정보들이 들어있다
@SpringBootApplication
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
- 여기서 우리가 주목할 애노테이션은 @EnableAutoConfiguration 이다.
- 이름 그대로 자동 구성을 활성화 하는 기능을 제공한다.
@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}

- @Import 는 주로 스프링 설정 정보( @Configuration )를 포함할 때 사용한다.
- 그런데 AutoConfigurationImportSelector 를 열어보면 @Configuration 이 아니다.
- 그냥 구현코드가 적혀있다.
- ImportSelector를 자세히 알면 해당 기능을 알 수 있다.
ImportSelector
@Import 에 설정 정보를 추가하는 방법은 2가지가 있다.
- 정적인 방법: @Import (클래스) 이것은 정적이다. 코드에 대상이 딱 박혀 있다. 설정으로 사용할 대상을 동적으로 변경할 수 없다.
- 동적인 방법: @Import ( ImportSelector ) 코드로 프로그래밍해서 설정으로 사용할 대상을 동적으로 선택할 수 있다
정적인 방법
@Configuration
@Import({AConfig.class, BConfig.class})
public class AppConfig {...}
- 그런데 예제처럼 AConfig , BConfig 가 코드에 딱 정해진 것이 아니라, 특정 조건에 따라서 설정 정보를 선택해야 하는 경우에는 어떻게 해야할까?
동적인 방법
ImportSelector
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
- 스프링은 설정 정보 대상을 동적으로 선택할 수 있는 ImportSelector 인터페이스를 제공한다.
ImportSelector 예제
HelloBean
public class HelloBean {
}
- 빈 등록 대상이다
HelloConfig
@Configuration
public class HelloConfig {
@Bean
public HelloBean helloBean(){
return new HelloBean();
}
}
- 설정 정보이다. HelloBean 을 스프링 빈으로 등록한다.
HelloImportSelector
public class HelloImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//해당 부분은 동적으로 변경할 수 있다 -> 코드이기 때문
return new String[]{"hello.selector.HelloConfig"};
}
}
- 설정 정보를 동적으로 선택할 수 있게 해주는 ImportSelector 인터페이스를 구현했다.
- 여기서는 단순히 hello.selector.HelloConfig 설정 정보를 반환한다.
- 이렇게 반환된 설정 정보는 선택되어서 사용된다.
- 여기에 설정 정보로 사용할 클래스를 동적으로 프로그래밍 하면 된다
ImportSelectorTest
public class ImportSelectorTest {
@Test
public void staticConfig() {
AnnotationConfigApplicationContext appContext =
new AnnotationConfigApplicationContext(StaticConfig.class);
HelloBean bean = appContext.getBean(HelloBean.class);
assertThat(bean).isNotNull();
}
@Test
public void selectorConfig() {
AnnotationConfigApplicationContext appContext =
new AnnotationConfigApplicationContext(SelectorConfig.class);
HelloBean bean = appContext.getBean(HelloBean.class);
assertThat(bean).isNotNull();
}
@Configuration
@Import(HelloConfig.class)
public static class StaticConfig {
}
@Configuration
@Import(HelloImportSelector.class)
public static class SelectorConfig {
}
}
- staticConfig()
- 스프링 컨테이너를 만들고, StaticConfig.class 를 초기 설정 정보로 사용했다.
- 그 결과 HelloBean 이 스프링 컨테이너에 잘 등록된 것을 확인할 수 있다.
- selectorConfig()
- selectorConfig() 는 SelectorConfig 를 초기 설정 정보로 사용한다.
- SelectorConfig 는 @Import(HelloImportSelector.class) 에서 ImportSelector 의 구현체인 HelloImportSelector 를 사용했다.
- 스프링은 HelloImportSelector 를 실행하고, "hello.selector.HelloConfig" 라는 문자를 반환 받는다.
- 스프링은 이 문자에 맞는 대상을 설정 정보로 사용한다. 따라서 hello.selector.HelloConfig 이 설정 정보로 사용된다.
- 그 결과 HelloBean 이 스프링 컨테이너에 잘 등록된 것을 확인할 수 있다.
@EnableAutoConfiguration 동작 방식
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
- AutoConfigurationImportSelector 는 ImportSelector 의 구현체이다. 따라서 설정 정보를 동적으로 선택할 수 있다.
- 실제로 이 코드는 모든 라이브러리에 있는 다음 경로의 파일을 확인한다.
- META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//...
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
//...
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
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);
}
//...
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
.getCandidates();
Assert.notEmpty(configurations,
"No auto configuration classes found in "
+ "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
//...
}

- DeferredImportSelector 때문에 selectImports가 바로 실행되지는 않지만, 중요하지는 않다
- getAutoConfigurationEntry 메서드를 살펴보면 getCandidateConfigurations 메서드를 통해 configuration 정보를 읽어온다.
- getCandidateConfigurations를 살펴보면 ImportCadidates.load()를 통해 설정 정보를 읽어 온다.
- ImportCadidates.load()를 샆펴보면, LOCATION 정보를 통해 위치를 읽어 오는 부분이 있는데 해당 부분이 아래로 치환된다
- org.springframework.boot.autoconfigure.AutoConfiguration.imports
- 결론적으로 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 하위의 문자열 정보를 설정 정보로 읽어 들여온다.
스프링 부트 자동 구성이 동작하는 방식은 다음 순서로 확인할 수 있다.
@SpringBootApplication -> @EnableAutoConfiguration -> @Import(AutoConfigurationImportSelector.class)
- resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일을 열어서 설정 정보 선택
- 해당 파일의 설정 정보가 스프링 컨테이너에 등록되고 사용
728x90
'스프링 부트(핵심 원리와 활용)' 카테고리의 다른 글
| Ch05. 외부설정과 프로필(1) - 프로젝트 설정 (0) | 2023.03.12 |
|---|---|
| Ch04. 자동 구성(Auto Configuration) - 정리 (0) | 2023.03.10 |
| Ch04. 자동 구성(Auto Configuration) - 자동 구성 라이브러리 사용하기(1) (0) | 2023.03.10 |
| Ch04. 자동 구성(Auto Configuration) - 자동 구성 라이브러리 만들기 (0) | 2023.03.10 |
| Ch04. 자동 구성(Auto Configuration) - 순수 라이브러리 사용하기 (0) | 2023.03.10 |