기본 콘텐츠로 건너뛰기

스프링 부트 AutoConfiguration은 어떻게 필요한 빈만 자동 등록할까

스프링 부트 AutoConfiguration은 어떻게 필요한 빈만 자동 등록할까

빠른 답

  • AutoConfiguration의 출발점은 @SpringBootApplication 내부의 @EnableAutoConfiguration이다.
  • AutoConfigurationImportSelector가 후보 설정을 수집하고 조건에 맞는 클래스만 남긴다.
  • 원하지 않는 자동 설정은 exclude, excludeName, spring.autoconfigure.exclude로 제거할 수 있다.
  • 빈 등록 결과가 예상과 다르면 --debug와 Condition Evaluation Report부터 확인하는 것이 가장 빠르다.

흐름으로 보기

스프링 부트 AutoConfiguration은 어떻게 필요한 빈만 자동 등록할까 흐름 다이어그램

핵심은 단순합니다. 스프링 부트가 "마법처럼 아무 빈이나 등록하는 것"이 아니라, 자동 설정 후보를 먼저 모으고, 애플리케이션 상태를 보고, 조건을 통과한 설정만 적용하는 구조입니다. 그래서 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는 대략 아래 순서로 움직입니다.

  1. 자동 설정 후보 목록을 읽는다.
  2. 중복을 제거한다.
  3. 사용자가 제외한 설정을 뺀다.
  4. 메타데이터와 조건을 바탕으로 걸러낸다.
  5. 살아남은 설정 클래스만 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입니다.

의존성을 추가하면 스프링 부트는 웹 애플리케이션을 구성할 수 있는 자동 설정 후보를 가져옵니다. 대표적으로 이런 설정들이 후보에 올라옵니다.

  • ServletWebServerFactoryAutoConfiguration
  • DispatcherServletAutoConfiguration
  • WebMvcAutoConfiguration
  • HttpMessageConvertersAutoConfiguration
  • JacksonAutoConfiguration
  • ErrorMvcAutoConfiguration

하지만 여기서 오해하면 안 됩니다. 스타터를 추가했다고 해서 위 설정들이 항상 전부 적용되는 것은 아닙니다. 조건을 통과해야만 실제로 작동합니다. 예를 들어 서블릿 환경이 아니면 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: 조건을 통과하지 못한 자동 설정
  • 실패 이유: 클래스 없음, 프로퍼티 값 불일치, 기존 빈 존재, 웹 환경 불일치 등

문제가 생겼을 때는 아래 순서로 보면 됩니다.

  1. 해당 자동 설정이 exclude로 빠진 것은 아닌지 본다.
  2. 클래스패스에 필요한 라이브러리가 실제로 있는지 확인한다.
  3. @ConditionalOnMissingBean 때문에 사용자 정의 빈이 우선된 것은 아닌지 본다.
  4. 프로퍼티 조건이 기대와 다르지 않은지 확인한다.
  5. @EnableWebMvc 같은 설정이 기본 자동 설정을 바꾸지 않았는지 점검한다.

이 순서를 지키면 "왜 안 되지?"라는 막연한 추측을 줄일 수 있습니다. AutoConfiguration 문제는 대부분 예외 메시지보다 조건 평가 로그가 더 직접적인 답을 줍니다.

실무에서 자주 하는 오해와 주의점

AutoConfiguration을 처음 배울 때 자주 생기는 오해를 정리하면 아래와 같습니다.

  • 자동 설정은 컴포넌트 스캔의 일부라고 생각하기 쉽다. 실제로는 ImportSelector가 별도로 가져온다.
  • 스타터를 추가하면 관련 빈이 모두 무조건 생긴다고 오해하기 쉽다. 실제로는 조건을 통과한 경우만 등록된다.
  • 자동 설정이 적용되면 사용자가 개입할 수 없다고 생각하기 쉽다. 대부분은 사용자 정의 빈이 우선한다.
  • 문제가 생기면 바로 자동 설정을 꺼버리기 쉽다. 먼저 조건 보고서로 원인을 확인하는 편이 더 낫다.
  • 오래된 글의 spring.factories 설명을 그대로 현재 버전에 대입하기 쉽다. 부트 3.x는 등록 방식이 달라졌다.

실무에서는 "어떤 자동 설정이 존재하느냐"보다 "왜 이 조건이 지금 true 또는 false가 되었느냐"를 읽는 능력이 더 중요합니다. 이 감각이 생기면 새로운 스타터를 붙였을 때도 막연히 기대하지 않고, 후보와 조건을 기준으로 동작을 예측할 수 있습니다.

정리

스프링 부트 AutoConfiguration은 마법이 아니라 규칙 기반 선택 시스템입니다. 시작점은 @SpringBootApplication 안의 @EnableAutoConfiguration이고, 실제 선택은 AutoConfigurationImportSelector가 담당합니다. 그다음에는 후보 자동 설정이 제외 설정과 조건 평가를 거치고, 마지막으로 필요한 빈만 등록됩니다.

그래서 AutoConfiguration을 잘 이해하려면 "어떤 빈이 등록되었는가"만 볼 것이 아니라, "왜 그 빈이 선택되었는가", 반대로 "왜 선택되지 않았는가"까지 같이 봐야 합니다. 이 관점이 잡히면 스프링 부트의 자동 설정은 더 이상 추상적인 기능이 아니라, 소스와 로그로 설명 가능한 동작 원리가 됩니다.

원문 참고

https://www.maeil-mail.kr/question/23

댓글

이 블로그의 인기 게시물

아이콘 폰트 (icomoon 사용법)

 장난감 프로젝트를 만들다 보면, 아이콘이 필요한 경우가 있다. 간단하게 아이콘을 인터넷에서 검색하여, 이미지로 넣어두고 이미지 태그를 이용하여, 사용하는 경우가 일반적이였지만...  요즘에는 대부분 폰트를 이용하여 아이콘을 노출 한다. 나 같은 경우에도 기본적으로  https://material.io/resources/icons 를 참고하여 아이콘 폰트를 이용할 수 있도록 처리하고, 추가적으로 필요한 아이콘이고, 일상적으로 사용 되지 않는 아이콘의 경우에는  https://icomoon.io 에서 제작하여, 아이콘 폰트로 이용 하곤 한다.  그래서 이번에는 아이콘  https://icomoon.io 의 사용법을 간단히 공유하고자 한다.   들어가자 마자 위의 icoMoonApp버튼을 누르면 아래와 같은 화면이 나타난다.  icomoon에서 무료로 제공하는 아이콘들이 보이면 위에 파란색으로 표시 되어있는 집 모양 세가지를 선택한 후, 아래의 빨간색으로 표시되어있는 Generate Font를 눌러보자.  그리고 나서 바로 다운로드를 요청해보자. icomoon.zip이 다운로드가 될텐데, 압축을 해제해 보면, 아래의 폴더 및 파일들이 있다. 아래에서 중요한 것은 font 폴더와 style.css이다. demo-files fonts demo.html Read Me.txt selection.json style.css <!doctype html > <html> <head> <link rel ="stylesheet" href ="style.css" ></head> </head> <body> <span class ="icon-home" ></span> <span class ="icon-home2" ></span> <span class ="icon-hom...

Chart js와 amchart 비교

Chart js 특징은 위의 그림으로 대체 할 수 있을 듯 하다. 오픈 소스이고, 기본으로 제공하는 차트 종류가 8가지 Canv a s를 이용해서 차트를 그리고, 반응형을 지원한다. amchart amchart는 기본적으로 유료이며, 기본으로 제공하는 차트 종류가 기본적인 차트 + 주식 처럼 보이는 차트 + 지도에 관련된 차트(?) 까지 하면, 기본 제공 하는 종류가 20개 내외 이려나, 일일이 세기에는 양이 좀 많아 보인다. 렌더링은 svg를 통하여 그려지고, 당연 반응형도 지원이 된다. 그러면, 이 둘중에 어떤것이 내 프로젝트에 적합 하냐는 것이 문제이다. 일단, 주식 처럼 보이는 차트나 지도에 관련된 차트(?)가 필요하면, amchart를 선택해야 되는 것은 맞다. 그건 당연한 것이니 빼고 얘기 해보자! 여러 종류의 차트가 필요하다면, 일단은 amchart를 염두해 두는 것이 좋다. 돈 낸 만큼은 하는 듯 하다. 하지만, 기본적인 막대 그래프, 도넛 차트 등, 아주 기본적인 차트들인데, Chart js도 amchart도 그러한 차트가 없을 때가 문제가 된다. 그렇다면, 조금이라도 커스텀이 용이한 것을 찾는 것이 좋을 것이다.  일단 amchart에서 custom이라고 검색 하였을 때, 검색 결과가 61가지가 나온다. 차트의 종류도 많고, 각 차트마다 들어가는 속성이 매우 많기 때문에, 웬만한 내용들은 속성 값을 어떻게 주느냐에 따라서 변경이 가능 하게 된다. 커스텀의 예를 들면, 기본적으로 도넛 파이의 형태를 띄면서, 화살표로 목표를 표시해주는 차트가 필요하다고 생각 해보자. 이것은 amchart로 만든 그래프이고 이것은 chart js로 만든 그래프이다. 모양이 살짝 다르긴 하지만, 완벽하게 똑같이 구현 할 수도 있다. amchart로 만든 그래프의 경우, 저것은 도넛그래프가 아닌 guage 그래프이다. 원래 게이지 그래프는 이와 같...

javascript 압축 파일 다운로드

이번에는 전 게시글의 응용판? 이라고 해야하나....? 어쨋든! 우리는 각각의 파일들을 다운로드 해보았다. 그런데 생각보다 귀찮음?을 느꼇을 것이다. 파일을 각각 다운 받아야 한다는 현실때문에! 그래 파일 두개야 뭐 그렇다 치지... 하지만, 개발자도 사용자도 게으름뱅이이다. 자 결국, 우리가 해야 하는 것은 파일을 한 번에 둘다! 다운 받는 것이다. 물론, 클릭 한번에 여러개의 함수를 엮어서 다운받게 하면 되지만! 크롬에서 자주 봤듯이, 여러개의 파일을 다운로드를 시도하면 <- 여러개의 파일을 다운로드 합니다. 허용 합니까? 하고 물어보는 것을 볼 수 있다. 게다가 다운로드 한 파일들을 찾기도 귀찮다는 것. 자 해결책을 제시해보자면, https://github.com/Stuk/jszip 클라이언트 단에서 파일을 zip파일로 압축을 할 수가 있다! 필요한 작업은 아래와 같다. 0. 데이터 준비 1. BLOB(binary large object)를 만든다. 2. Blob을 URL.createObjectURL을 사용하여, 해당 binary의 주소를 생성. 3. 다운로드가 필요한 파일들을 Zip 객체에 셋팅! 4. a태그를 이용하여, 해당 url 셋팅 하고, 다운로드. 전 게시물과 별로 달라진게 없네... 자 그럼 샘플! 샘플을 보자! http://embed.plnkr.co/NMprnRxqYG0fkHa2J55D/ var util = {} function fixBinary(bin) { //binary to arrayBuffer var length = bin.length var buf = new ArrayBuffer(length) var arr = new Uint8Array(buf) for (var i = 0; i < length; i++) { arr[i] = bin.charCodeAt(i) } return buf } window.onload = function() { ...