본문으로 건너뛰기
안동민 개발노트 아이콘

안동민 개발노트

본문 시작
14장 : Spring Boot와 외부 설정

자동 구성 과정

@EnableAutoConfiguration에서 후보 메타데이터·지연된 가져오기·조건 평가·빈 등록으로 이어지는 경로를 확인하고 @AutoConfiguration 순서 지정과 사용자 재정의를 독립 모듈로 구현합니다.

자동 구성은 컴포넌트 스캔이 우연히 라이브러리 구성을 찾는 방식이 아닙니다. Boot가 지정된 메타데이터에서 후보를 읽고 조건을 평가해 구성을 불러옵니다. 애플리케이션 스캔 패키지 밖에 라이브러리가 있어도 동작하고, 사용자 빈이 있으면 기본값이 물러나도록 작성해야 재사용 가능한 통합이 됩니다.


EnableAutoConfiguration 후보

애플리케이션 구성 파싱 중 자동 구성 선택기가 환경과 클래스 로더를 사용해 후보를 찾고 제외와 필터를 적용합니다. 일반 사용자 구성이 먼저 처리된 뒤 자동 구성을 적용하므로 @ConditionalOnMissingBean 조건이 재정의를 존중합니다.

@SpringBootApplication이 활성화 애노테이션을 포함하지만 테스트 슬라이스나 사용자 정의 컨텍스트에서는 범위가 다를 수 있습니다. 자동 구성 단위 테스트는 ApplicationContextRunner처럼 필요한 후보를 명시합니다.

src/main/java/board/autoconfig/AutoConfigInputs.java
package board.autoconfig;

import java.util.Set;

public record AutoConfigInputs(
        Set<String> candidates,
        Set<String> exclusions,
        Set<String> presentClasses
) {
    public AutoConfigInputs {
        candidates = Set.copyOf(candidates);
        exclusions = Set.copyOf(exclusions);
        presentClasses = Set.copyOf(presentClasses);
    }

    public Set<String> eligible() {
        return candidates.stream()
                .filter(candidate -> !exclusions.contains(candidate))
                .collect(java.util.stream.Collectors.toUnmodifiableSet());
    }
}

이 모델은 실제 Boot 선택기를 대체하지 않지만 후보와 제외가 서로 다른 입력이라는 사실을 보여 줍니다. 조건은 적용 대상 구성 안에서 추가로 평가됩니다.

자동 구성의 스캔 범위

라이브러리 구성은 명시적 후보 메타데이터에 등록하고 애플리케이션이 직접 불러올 공개 구성 API와 섞지 않습니다. 잘못 컴포넌트 스캔되면 조건 보고서와 순서 지정을 우회해 두 번 등록될 수 있습니다.

src/main/java/board/autoconfig/BoardClockAutoConfiguration.java
package board.autoconfig;

import java.time.Clock;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;

@AutoConfiguration
public class BoardClockAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean(Clock.class)
    Clock boardClock() {
        return Clock.systemUTC();
    }
}

사용자가 Clock.fixed 빈을 제공하면 기본값이 생성되지 않습니다. 빈 이름보다 타입을 기준으로 기본값을 양보할지, 한정자별로 여러 시계를 허용할지 라이브러리 규칙에서 정합니다.

자동 구성 메타데이터

자동 구성 모듈의 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports에 완전한 이름으로 구성 클래스를 한 줄씩 등록합니다. 소스 클래스만 만들고 메타데이터를 빠뜨리면 사용자 클래스 경로에 JAR이 있어도 후보가 0입니다.

candidate metadata 누락 실패
library class present=true
AutoConfiguration.imports entry=false
candidate count=0
Clock bean created=false
result=AUTO_CONFIGURATION_NOT_IMPORTED

릴리스 JAR 파일을 검증할 때 메타데이터 항목과 클래스 로딩을 확인합니다. 셰이딩이나 재배치가 클래스 이름을 바꾸면 메타데이터도 함께 바뀌어야 합니다.

자동 구성 순서

beforeafter는 구성 처리 순서를 조정하지만 빈 초기화 순서나 런타임 호출 순서와 같지 않습니다. 실제 의존성은 빈 메서드 파라미터와 조건으로 표현합니다.

다른 자동 구성이 만든 빈이 있을 때만 적용하려면 클래스 이름 순서 지정과 빈 조건을 함께 사용합니다. 순환 순서 지정을 만들지 않고 이유를 테스트 이름에 남깁니다.

사용자 구성이 최종 우선권을 가지도록 무조건 등록되는 기본 빈을 피합니다. 빈 정의 덮어쓰기를 활성화해야만 교체되는 방식보다 빈이 없을 때만 기본값을 등록하는 구성이 조합에 안전합니다.

조건 보고서

후보 자체가 없으면 보고서에 구성이 나타나지 않을 수 있습니다. 후보는 있지만 클래스 조건이 false면 부정 일치가 보입니다. 조건은 true인데 빈 팩토리 메서드가 실패하면 생성 예외와 원인 연쇄를 봅니다.

--debug 또는 조건 평가 보고서 로깅은 진단에 유용하지만 비밀 정보 속성 값을 출력하지 않습니다. 일치 결과 메시지는 무엇을 찾았고 무엇이 없었는지 구체적으로 작성합니다.

시작 테스트는 기본 빈 생성, 사용자 재정의에 따른 기본값 미등록, 속성 비활성화, 클래스 누락이라는 네 시나리오를 분리합니다. 하나의 전체 애플리케이션 컨텍스트만으로 모든 분기를 확인하지 않습니다.

진단 순서는 후보 발견 여부부터 시작합니다. 먼저 AutoConfiguration.imports 메타데이터와 제외 설정으로 후보가 되었는지, 다음으로 클래스·속성 같은 구성 수준 조건이 통과했는지, 마지막으로 빈 메서드의 @ConditionalOnMissingBean 조건과 생성 예외를 봅니다. 이 순서를 거꾸로 추적하면 존재하지도 않는 빈 메서드를 오래 디버깅하게 됩니다. ConditionEvaluationReport의 긍정 일치는 “빈 생성 성공”이 아니라 해당 조건이 참이었다는 뜻이고, 무조건 적용되는 클래스도 별도 묶음으로 나타날 수 있습니다. 따라서 보고서와 실제 빈 그래프, 시작 실패 원인을 함께 읽습니다.

condition report 해석 순서
candidate absent -> imports/exclusion/package artifact 확인
candidate present + negative configuration match -> class/property/resource 입력 확인
configuration positive + bean negative match -> user bean과 search scope 확인
bean condition positive + startup failure -> factory method cause chain 확인

운영 애플리케이션에서 --debug를 상시 켜기보다 실패를 재현한 작은 컨텍스트의 보고서를 산출물로 남깁니다. 같은 구성이 긍정과 부정 양쪽에 섞여 보이면 중첩 조건과 빈 메서드 조건을 구분합니다. 릴리스 검증은 이전 버전과 결과 차이를 비교해 의존성 업그레이드가 후보를 새로 켜거나 끄지 않았는지 확인합니다.

공개 API·내부 빈

사용자가 주입해도 되는 타입과 구성 속성은 문서화하고 내부 도우미 빈 이름에는 의존하지 않게 합니다. 버전 업그레이드에서 내부 그래프를 바꿔도 공개 기능이 유지되어야 합니다.

구성 클래스 자체를 사용자가 직접 불러오면 조건 순서 지정과 메타데이터가 달라질 수 있습니다. 테스트 도우미가 필요하면 별도 공개 테스트 구성을 제공합니다.

연습 문제

UTC 시계와 요청 ID 생성기를 제공하는 자동 구성을 설계하세요. 라이브러리 클래스 존재, 속성 활성화, 사용자 시계 재정의, AutoConfiguration.imports 메타데이터, 순서 지정, ApplicationContextRunner 분기를 모두 표로 만들고 기본값과 재정의가 동시에 생기지 않게 하세요.

해설 보기

요청 ID 생성기가 시계에 의존하면 빈 파라미터로 관계를 드러냅니다. 구성 순서 지정만으로 먼저 만들어질 것이라 기대하지 않습니다.

package board.autoconfig;

import java.time.Clock;

public final class RequestIdGenerator {
    private final Clock clock;

    public RequestIdGenerator(Clock clock) {
        this.clock = clock;
    }

    public String next(long sequence) {
        return clock.instant().toEpochMilli() + "-" + sequence;
    }
}

sequence 값을 여러 스레드가 공유한다면 원자적 카운터나 영속적인 발급원을 사용하고 ID를 메트릭 태그로 넣지 않습니다.