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

안동민 개발노트

본문 시작
2장 : Bean과 의존성 주입

컴포넌트 스캔과 명시적 Configuration

Spring이 모든 클래스를 자동으로 객체화하는 것은 아닙니다. @Component 계열 annotation이 붙고 애플리케이션의 탐색 패키지 아래에 있는 클래스만 기본 스캔 대상이 됩니다. 반면 소스에 annotation을 붙일 수 없는 JDK·외부 라이브러리 타입은 구성 클래스의 @Bean 메서드로 등록합니다.

등록 방식보다 더 중요한 것은 후보가 여러 개일 때의 의도입니다. 타입만으로 하나를 고를 수 없다면 이름과 @Qualifier, 또는 기본 후보를 나타내는 @Primary 중 하나로 선택을 명확히 해야 합니다.


발견 범위는 패키지 경계와 함께 설계한다

컴포넌트 스캔은 우리 애플리케이션 타입을 관례로 발견하는 데 편리합니다. 명시적 Configuration은 생성 인자와 수명 정책을 직접 통제해야 하는 외부 객체에 적합합니다. 두 방법으로 등록된 Bean은 컨테이너 안에서는 동등한 후보이므로, 같은 타입이 둘 이상이면 주입 지점이 선택 근거를 제공해야 합니다.


스캔 대상과 외부 객체 구성을 분리한다

날짜 표기법 두 개를 Bean으로 등록하고, SessionLabeler가 ISO 형식을 원한다고 생성자에서 분명히 밝힙니다. 이렇게 하면 나중에 화면용 형식이 추가되어도 기존 서비스의 선택이 우연히 바뀌지 않습니다.

src/main/java/com/andongmin/studylog/FormattingConfiguration.java
package com.andongmin.studylog;

import java.time.format.DateTimeFormatter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class FormattingConfiguration {
    @Bean
    DateTimeFormatter studyDateFormatter() {
        return DateTimeFormatter.ISO_LOCAL_DATE;
    }

    @Bean
    DateTimeFormatter humanDateFormatter() {
        return DateTimeFormatter.ofPattern("yyyy년 M월 d일");
    }
}
src/main/java/com/andongmin/studylog/session/SessionLabeler.java
package com.andongmin.studylog.session;

import java.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class SessionLabeler {
    private final DateTimeFormatter formatter;

    public SessionLabeler(
            @Qualifier("studyDateFormatter") DateTimeFormatter formatter) {
        this.formatter = formatter;
    }

    public String label(StudySession session) {
        return formatter.format(session.studyDate()) + " " + session.subject();
    }
}

@Bean 메서드 이름이 기본 Bean 이름이므로 qualifier 문자열과 정확히 일치해야 합니다. 두 번째 formatter를 추가하고 qualifier를 제거해 보면 컨테이너가 모호한 의존성을 즉시 거부합니다. 이것은 임의의 후보를 골라 잘못된 형식을 내보내는 것보다 안전한 실패입니다.

Bean 목록과 이름 충돌을 점검한다

디버그 로그 전체를 외우기보다, 시작 성공 여부와 주입 후보 이름을 확인합니다. 이름 충돌 오류가 나면 annotation을 더 붙이기 전에 같은 타입이 왜 여러 개 필요한지부터 검토합니다.

./mvnw -q -DskipTests compile
./mvnw -q spring-boot:run -Dspring-boot.run.arguments=--debug
Qualifier 확인 결과
두 DateTimeFormatter Bean 중 `studyDateFormatter`가 SessionLabeler에 주입되고 애플리케이션이 시작되어야 합니다.

Bean 조립 규칙을 확인했으니 이제 HTTP 요청이 여러 MVC 구성요소를 지나 Controller에 도달하는 순서를 살펴봅니다.