스프링 부트 AutoConfiguration은 어떻게 필요한 빈만 자동 등록할까
빠른 답
- AutoConfiguration의 출발점은 @SpringBootApplication 내부의 @EnableAutoConfiguration이다.
- AutoConfigurationImportSelector가 후보 설정을 수집하고 조건에 맞는 클래스만 남긴다.
- 원하지 않는 자동 설정은 exclude, excludeName, spring.autoconfigure.exclude로 제거할 수 있다.
- 빈 등록 결과가 예상과 다르면 --debug와 Condition Evaluation Report부터 확인하는 것이 가장 빠르다.
목차
흐름으로 보기
핵심은 단순합니다. 스프링 부트가 "마법처럼 아무 빈이나 등록하는 것"이 아니라, 자동 설정 후보를 먼저 모으고, 애플리케이션 상태를 보고, 조건을 통과한 설정만 적용하는 구조입니다. 그래서 AutoConfiguration은 감으로 이해하기보다 "후보 수집 -> 조건 평가 -> 빈 등록"의 흐름으로 보는 편이 훨씬 정확합니다.
왜 AutoConfiguration이 필요한가: 반복 설정을 줄이는 기본 전략
순수 스프링만 사용할 때는 웹 서버, JSON 직렬화기, 메시지 컨버터, 데이터소스, 트랜잭션 매니저 같은 인프라 빈을 직접 등록하는 경우가 많았습니다. 문제는 이런 설정이 프로젝트마다 거의 비슷하다는 점입니다. 팀마다 클래스 이름만 다르고, 실제 의도는 늘 비슷합니다.
스프링 부트의 AutoConfiguration은 이 반복을 줄이기 위해 만들어졌습니다. 클래스패스에 어떤 라이브러리가 있는지, 현재 애플리케이션이 서블릿 기반인지 리액티브 기반인지, 사용자가 이미 같은 역할의 빈을 만들었는지, 특정 프로퍼티가 켜져 있는지를 보고 기본 구성을 제안합니다.
중요한 포인트는 "강제"가 아니라 "기본값 제공"이라는 점입니다. 부트는 먼저 합리적인 기본 설정을 깔아주고, 사용자가 직접 빈을 정의하면 대체로 뒤로 물러섭니다. 그래서 실무에서는 AutoConfiguration을 자동 생성기보다 조건부 기본 설정 세트로 이해하는 편이 좋습니다.
@SpringBootApplication에서 시작되는 자동 설정 진입점
대부분의 부트 애플리케이션 시작 클래스는 아래와 비슷합니다.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
겉보기에는 단순하지만, 이 어노테이션 하나에 자동 설정의 진입점이 들어 있습니다. @SpringBootApplication은 크게 세 가지 역할을 묶습니다.
@SpringBootConfiguration@ComponentScan@EnableAutoConfiguration
여기서 AutoConfiguration의 출발점은 @EnableAutoConfiguration입니다. 이 어노테이션 안에는 @Import(AutoConfigurationImportSelector.class)가 들어 있어, 자동 설정 클래스를 직접 골라서 가져오도록 연결됩니다.
개념상 중요한 부분만 줄이면 이런 형태입니다.
@Target(TYPE)
@Retention(RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
여기서 볼 수 있는 사실은 두 가지입니다.
첫째, 자동 설정은 일반적인 컴포넌트 스캔으로 찾아지는 것이 아닙니다. ImportSelector가 후보를 계산해서 가져옵니다.
둘째, exclude, excludeName은 자동 설정을 공식적으로 끄는 진입점입니다.
즉, 애플리케이션이 시작되면 스프링 부트는 "어떤 자동 설정을 적용할지"를 먼저 계산하고, 그 결과를 ApplicationContext에 반영합니다.
AutoConfigurationImportSelector는 어떤 순서로 후보를 고를까
이제 핵심 동작 원리를 봐야 합니다. AutoConfigurationImportSelector는 대략 아래 순서로 움직입니다.
- 자동 설정 후보 목록을 읽는다.
- 중복을 제거한다.
- 사용자가 제외한 설정을 뺀다.
- 메타데이터와 조건을 바탕으로 걸러낸다.
- 살아남은 설정 클래스만
Import한다.
이 과정에서 자주 놓치는 부분이 있습니다. 스프링 부트는 후보를 무작정 전부 로딩한 다음 검사하지 않습니다. 가능한 경우 메타데이터를 먼저 보고, 애초에 성립할 수 없는 자동 설정을 초기에 탈락시킵니다. 예를 들어 특정 라이브러리 클래스가 클래스패스에 없으면, 그 라이브러리에 의존하는 자동 설정은 일찍 제외됩니다.
또 하나 중요한 버전 차이도 있습니다.
- 스프링 부트 2.x의 오래된 설명에서는
spring.factories를 자주 본다. - 스프링 부트 3.x에서는 자동 설정 후보를 주로
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports에서 읽는다.
그래서 예전 글을 볼 때 spring.factories 설명만 있다면, 현재 사용하는 부트 버전과 맞는지 한 번 더 확인하는 것이 좋습니다.
개념을 단순화하면 내부 흐름은 이런 느낌입니다.
List<String> configurations = getCandidateConfigurations(metadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(metadata, attributes);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
return configurations.toArray(String[]::new);
실제 소스는 더 복잡하지만, 이해 포인트는 명확합니다. AutoConfiguration의 본질은 "후보 목록을 모아 둔 뒤 조건에 맞는 것만 통과시키는 선택 과정"입니다.
조건부 자동 설정은 어떻게 읽어야 할까
자동 설정 클래스가 Import 대상으로 남았다고 해서, 그 안의 모든 빈이 무조건 등록되는 것은 아닙니다. 자동 설정 클래스 자체에도 조건이 붙고, 그 안의 개별 @Bean 메서드에도 조건이 붙습니다.
실무에서 가장 자주 보는 조건은 아래 다섯 가지입니다.
@ConditionalOnClass@ConditionalOnMissingBean@ConditionalOnBean@ConditionalOnProperty@ConditionalOnWebApplication
읽는 순서는 이렇게 잡으면 편합니다.
- 필요한 클래스가 클래스패스에 있는가
- 특정 프로퍼티가 켜져 있는가
- 이미 사용자가 같은 타입 또는 같은 이름의 빈을 만들었는가
- 현재 애플리케이션이 서블릿 웹인가, 리액티브 웹인가
- 선행 빈이 먼저 등록되어 있는가
예를 들어 아래 코드는 자동 설정이 어떻게 물러나는지를 잘 보여줍니다.
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@AutoConfiguration
@ConditionalOnClass(ObjectMapper.class)
class ExampleJacksonAutoConfiguration {
@Bean
@ConditionalOnMissingBean
ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
@Configuration
class UserJacksonConfig {
@Bean
ObjectMapper objectMapper() {
return new ObjectMapper().findAndRegisterModules();
}
}
이 예시에서 자동 설정은 ObjectMapper가 클래스패스에 있을 때만 의미가 있습니다. 하지만 사용자가 ObjectMapper 빈을 이미 등록했으므로, @ConditionalOnMissingBean 때문에 자동 설정은 해당 빈을 더 만들지 않습니다.
실무에서 "왜 부트가 이 빈을 자동 등록하지 않았지?"라는 질문의 답은 상당수가 여기에 있습니다. 이미 같은 역할의 빈이 있었거나, 프로퍼티가 꺼져 있었거나, 애플리케이션 타입이 기대와 달랐기 때문입니다.
실전 예시: spring-boot-starter-web을 추가하면 무엇이 일어날까
AutoConfiguration을 이해하려면 스타터 의존성이 실제로 어떤 효과를 내는지 보는 것이 좋습니다. 가장 흔한 예시는 spring-boot-starter-web입니다.
의존성을 추가하면 스프링 부트는 웹 애플리케이션을 구성할 수 있는 자동 설정 후보를 가져옵니다. 대표적으로 이런 설정들이 후보에 올라옵니다.
ServletWebServerFactoryAutoConfigurationDispatcherServletAutoConfigurationWebMvcAutoConfigurationHttpMessageConvertersAutoConfigurationJacksonAutoConfigurationErrorMvcAutoConfiguration
하지만 여기서 오해하면 안 됩니다. 스타터를 추가했다고 해서 위 설정들이 항상 전부 적용되는 것은 아닙니다. 조건을 통과해야만 실제로 작동합니다. 예를 들어 서블릿 환경이 아니면 MVC 관련 자동 설정은 적용되지 않고, 사용자가 직접 등록한 빈이 있으면 일부 기본 빈은 빠집니다.
아래처럼 간단한 컨트롤러만 있어도, 많은 기반 빈은 AutoConfiguration이 대신 준비해 줍니다.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "ok";
}
}
이 코드가 동작하려면 내부적으로 DispatcherServlet, 핸들러 매핑, 핸들러 어댑터, HTTP 메시지 컨버터, 내장 톰캣 같은 구성이 맞물려야 합니다. 스프링 부트는 바로 그 반복 구성을 자동 설정으로 묶어 둔 것입니다.
여기서 자주 헷갈리는 사례도 함께 기억해 둘 만합니다.
WebMvcConfigurer를 추가했다고 MVC 자동 설정 전체가 꺼지는 것은 아니다.@EnableWebMvc를 붙이면 부트의 기본 MVC 설정을 상당 부분 직접 제어하는 방향으로 바뀐다.- JSON 응답 문제가 꼭
JacksonAutoConfiguration실패 때문은 아니다. - 커스텀
ObjectMapper, 메시지 컨버터 재정의, 빈 우선순위가 원인인 경우도 많다.
결국 스타터는 "후보를 늘려주는 장치"이고, 최종 결과는 조건 평가가 결정합니다.
자동 설정을 끄거나 바꾸는 실전 설정 방법
원하지 않는 자동 설정은 명시적으로 제외할 수 있습니다. 가장 많이 쓰는 방법은 애너테이션과 프로퍼티 두 가지입니다.
먼저 애너테이션 방식입니다.
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DemoApplication {
}
다음은 설정 파일 방식입니다. 운영 환경별로 조정해야 할 때 특히 편합니다.
debug: true
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
management:
endpoints:
web:
exposure:
include: conditions
이 설정에서 봐야 할 포인트는 세 가지입니다.
spring.autoconfigure.exclude로 자동 설정을 끌 수 있다.debug: true로 조건 평가 보고서를 더 쉽게 본다.- Actuator의
conditions엔드포인트를 열면 런타임에서 어떤 조건이 통과했는지 확인할 수 있다.
자동 설정을 무조건 끄는 것은 신중해야 합니다. 당장 문제를 우회하는 데는 도움이 되지만, 나중에 다른 스타터나 설정이 기대하는 기본 빈까지 사라질 수 있기 때문입니다. 가능하면 먼저 왜 해당 자동 설정이 적용되는지, 정말 끄는 것이 맞는지를 확인한 뒤 결정하는 편이 안전합니다.
디버깅: 왜 어떤 빈은 등록되고 어떤 빈은 빠질까
AutoConfiguration을 이해하는 가장 빠른 방법은 로그를 보는 것입니다. 특히 빈이 "안 생긴 이유"를 추적할 때는 감보다 Condition Evaluation Report가 훨씬 정확합니다.
가장 기본적인 확인 방법은 아래와 같습니다.
./gradlew bootRun --args='--debug'
java -jar build/libs/app.jar --debug
curl http://localhost:8080/actuator/conditions
--debug로 실행하면 시작 로그에 조건 평가 보고서가 나옵니다. 여기에 어떤 자동 설정이 적용되었는지, 어떤 조건이 실패했는지가 정리됩니다. 형태는 대략 이렇게 보입니다.
Positive matches: 조건을 통과한 자동 설정Negative matches: 조건을 통과하지 못한 자동 설정- 실패 이유: 클래스 없음, 프로퍼티 값 불일치, 기존 빈 존재, 웹 환경 불일치 등
문제가 생겼을 때는 아래 순서로 보면 됩니다.
- 해당 자동 설정이
exclude로 빠진 것은 아닌지 본다. - 클래스패스에 필요한 라이브러리가 실제로 있는지 확인한다.
@ConditionalOnMissingBean때문에 사용자 정의 빈이 우선된 것은 아닌지 본다.- 프로퍼티 조건이 기대와 다르지 않은지 확인한다.
@EnableWebMvc같은 설정이 기본 자동 설정을 바꾸지 않았는지 점검한다.
이 순서를 지키면 "왜 안 되지?"라는 막연한 추측을 줄일 수 있습니다. AutoConfiguration 문제는 대부분 예외 메시지보다 조건 평가 로그가 더 직접적인 답을 줍니다.
실무에서 자주 하는 오해와 주의점
AutoConfiguration을 처음 배울 때 자주 생기는 오해를 정리하면 아래와 같습니다.
- 자동 설정은 컴포넌트 스캔의 일부라고 생각하기 쉽다. 실제로는
ImportSelector가 별도로 가져온다. - 스타터를 추가하면 관련 빈이 모두 무조건 생긴다고 오해하기 쉽다. 실제로는 조건을 통과한 경우만 등록된다.
- 자동 설정이 적용되면 사용자가 개입할 수 없다고 생각하기 쉽다. 대부분은 사용자 정의 빈이 우선한다.
- 문제가 생기면 바로 자동 설정을 꺼버리기 쉽다. 먼저 조건 보고서로 원인을 확인하는 편이 더 낫다.
- 오래된 글의
spring.factories설명을 그대로 현재 버전에 대입하기 쉽다. 부트 3.x는 등록 방식이 달라졌다.
실무에서는 "어떤 자동 설정이 존재하느냐"보다 "왜 이 조건이 지금 true 또는 false가 되었느냐"를 읽는 능력이 더 중요합니다. 이 감각이 생기면 새로운 스타터를 붙였을 때도 막연히 기대하지 않고, 후보와 조건을 기준으로 동작을 예측할 수 있습니다.
정리
스프링 부트 AutoConfiguration은 마법이 아니라 규칙 기반 선택 시스템입니다. 시작점은 @SpringBootApplication 안의 @EnableAutoConfiguration이고, 실제 선택은 AutoConfigurationImportSelector가 담당합니다. 그다음에는 후보 자동 설정이 제외 설정과 조건 평가를 거치고, 마지막으로 필요한 빈만 등록됩니다.
그래서 AutoConfiguration을 잘 이해하려면 "어떤 빈이 등록되었는가"만 볼 것이 아니라, "왜 그 빈이 선택되었는가", 반대로 "왜 선택되지 않았는가"까지 같이 봐야 합니다. 이 관점이 잡히면 스프링 부트의 자동 설정은 더 이상 추상적인 기능이 아니라, 소스와 로그로 설명 가능한 동작 원리가 됩니다.
원문 참고
https://www.maeil-mail.kr/question/23
댓글
댓글 쓰기