@SpringBootApplication과 AutoConfiguration 들여다보기

kindof

·

2023. 10. 7. 17:24

SpringBoot 프로젝트를 생성하면 @SpringBootApplication 어노테이션이 붙은 Application 클래스가 생성됩니다.

 

그리고 @SpringBootApplication은 크게 세 가지 어노테이션(@SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan)을 포함하고 있는데요.

 

이번 글에서는 SpringBoot 애플리케이션의 동작의 가장 시작점에 있는 각 어노테이션이 하는 역할을 살펴보고, SpringBoot의 AutoConfiguration 기능의 동작 원리에 알아보겠습니다.

@SpringBootApplication > @EnableAutoConfiguration

 

1. @SpringBootConfiguration

@SpringBootConfiguration 어노테이션을 들여다보면 아래처럼 @Configuration과 동일한 기능을 합니다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {

   @AliasFor(annotation = Configuration.class)
   boolean proxyBeanMethods() default true;

}

그리고 Spring 공식 문서를 따라가보면 아래와 같이 @SpringBootConfiguration을 소개하고 있는데요.

 

Indicates that a class provides Spring Boot application @Configuration. Can be used as an alternative to the Spring's standard @Configuration annotation so that configuration can be found automatically (for example in tests).

Application should only ever include one @SpringBootConfiguration and most idiomatic Spring Boot applications will inherit it from @SpringBootApplication.

 

@SpringBootConfiguration은 @Configuration과 동일한 역할을 하지만, 전체 애플리케이션 내에서 한 곳에만 위치하여 그 하위의 Configuration들을 자동으로 로드할 수 있게 한다는 것입니다.

@SpringBootApplication
|
 -- @SpringBootConfiguration
  |
   -- @Configuration

 

실제로 @SpringBootTest 내 108 Line을 보면 아래와 같은 내용이 있습니다.

/**
 * The <em>component classes</em> to use for loading an
 * {@link org.springframework.context.ApplicationContext ApplicationContext}. Can also
 * be specified using
 * {@link ContextConfiguration#classes() @ContextConfiguration(classes=...)}. If no
 * explicit classes are defined the test will look for nested
 * {@link Configuration @Configuration} classes, before falling back to a
 * {@link SpringBootConfiguration @SpringBootConfiguration} search.
 * @see ContextConfiguration#classes()
 * @return the component classes used to load the application context
 */

@SpringBootTest를 사용하면 ApplicationContext를 로드하여 사용할 수 있는데, @ContextConfiguration으로 대체하지 않는 한 그 안에는 @SpringBootConfiguration 하위의 @Configuration 어노테이션이 붙은 클래스들을 자동으로 로딩된다는 뜻입니다.

 

 

 

2. @ComponentScan(excludeFilters = ...)

Spring은 기본적으로 @Component, @Controller, @Service, @Repository, @Aspect... 등의 어노테이션이 붙은 객체들을 컴포넌트 스캔 대상으로 합니다.

 

이 때, @ComponentScan은 Bean을 등록하기 위한 어노테이션을 탐색하는 위치를 지정하는데, basePackage를 지정하지 않으면 기본적으로 해당 어노테이션이 붙은 클래스를 기준 하위 클래스들을 스캔하게 됩니다.

 

따라서, 가장 최상위의 @SpringBootApplication에 @ComponentScan을 둠으로써 전체 프로젝트 하위를 기본적인 컴포넌트 스캔 대상으로 삼는 것이죠.

 

그런데 @SpringBootApplication 하위의 @ComponentScan 어노테이션에는 아래와 같이 부가적인 Filter 관련 내용이 있습니다.

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

TypeExcludeFilter, AutoConfigurationExcludeFilter 타입의 클래스를 컴포넌트 스캔 대상에서 제외한다는 뜻인데요.

 

이처럼 @ComponentScan 어노테이션을 조작하여 패키지 단위 뿐만 아니라, 커스텀한 기준으로도 스캔 범위를 조정할 수 있다는 것을 알 수 있습니다.

 

 

3. @EnableAutoConfiguration

위에서 설명한 것처럼 Springboot는 Component scan을 통해 개발자가 정의한 Component들(@Controller, @Service 등)을 찾고 Bean을 생성합니다.

 

그리고 Springboot는 이 과정이 끝나면 spring.factories 파일에 정의한 AutoConfiguration 내용을 바탕으로 추가적인 Bean을 생성합니다.

 

이 때, AutoConfiguration의 대상은 org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일에 정의되어 있습니다.

org.springframework.boot.autoconfigure.AutoConfiguration.imports

 

위 파일에 정의된 많은 대상 중에서 JpaRepositoriesAutoConfiguration 파일을 열어보겠습니다.

@AutoConfiguration(after = { HibernateJpaAutoConfiguration.class, TaskExecutionAutoConfiguration.class })
@ConditionalOnBean(DataSource.class)
@ConditionalOnClass(JpaRepository.class)
@ConditionalOnMissingBean({ JpaRepositoryFactoryBean.class, JpaRepositoryConfigExtension.class })
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "enabled", havingValue = "true",
      matchIfMissing = true)
@Import(JpaRepositoriesImportSelector.class)
public class JpaRepositoriesAutoConfiguration {

코드를 보면 @ConditionalOnBean, @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty 등에 대한 내용이 있는데요.

 

 

spring.factories에는 Auto Configuration을 할 때 참고할 Import Filter를 정의해놓은 부분이 있습니다. 

spring.factories

코드를 보면 OnBeanCondititon, OnClassCondition, OnWebApplicationCondition 세 가지를 확인할 수 있죠.

 

예를 들어, org.springframework.boot.autoconfigure.condition.OnClassCondition은 특정 클래스의 존재 유무를 바탕으로 한 필터입니다.

 

이 맥락으로 보면, 위에서 소개한 JpaRepositoriesAutoConfiguration에 있는 @ConditionalOnClass(JpaRepository.class) 어노테이션은 classpath에 JpaRepository 클래스가 존재하는 경우에만 AutoConfiguration의 대상이 되게 강제합니다.

 

마찬가지로, @ConditionalOnBean(DataSource.class) 조건은 DataSource 클래스가 Bean으로 등록이 되어있어야만 AutoConfiguration의 대상이 되도록 필터링하죠.

 

JpaRepositories를 사용하기 위해서는 반드시 DataSource가 필요하다는 것을 생각해보면 자연스럽게 이해할 수 있습니다.

 

실제로 아래 테스트 코드를 통해 ApplicationContext에 등록된 Bean들의 목록을 확인할 수 있는데요.

@SpringBootTest
class ProjectApplicationTests {
   @Autowired
   private ApplicationContext applicationContext;

   @Test
   public void contextLoads() throws Exception {
      if (applicationContext != null) {
         String[] beans = applicationContext.getBeanDefinitionNames();

         for (String bean : beans) {
            System.out.println("bean : " + bean);
         }
      }
   }
}

JpaRepositoriesAutoConfiguration

위에서 예시로 봤던 JpaRepositoresAutoConfiguration이 Bean으로 등록되어 있는 것을 알 수 있고, 이는 AutoConfiguration의 결과라는 것을 이제 알 수 있습니다.

 

 

4. 정리 / Reference

이렇게 이번 글에서는 @SpringBootApplication에 포함된 세 가지 어노테이션 @SpringBootConfiguration, @ComponentScan, @EnableAutoConfiguration에 대해 알아봤습니다.

 

Springboot에서 제공해주는 AutoConfiguration 기능이 어떻게 동작하는지 이해하면 잘못된 프로젝트의 의존성, 필요한 클래스나 빈 미등록으로부터 파생되는 문제들을 해결할 수 있으리라 생각합니다.

 

감사합니다.

 

https://tecoble.techcourse.co.kr/post/2021-10-14-springboot-autoconfiguration/

https://www.baeldung.com/spring-componentscan-filter-type

https://velog.io/@roycewon/Component-Scan%EC%97%90-%EA%B4%80%ED%95%98%EC%97%AC

http://dveamer.github.io/backend/SpringBootAutoConfiguration.html