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

안동민 개발노트

본문 시작
3장 : 컨테이너·빈·생명주기·스코프

의존성 후보 선택

같은 PasswordHasher 타입의 현재·레거시 구현을 @Primary, @Qualifier, 컬렉션 주입으로 선택하고 선택 근거를 코드에 남깁니다.

같은 타입의 빈이 둘 이상이면 컨테이너는 하나를 임의로 고르지 않습니다. 게시판이 새 비밀번호 해시와 이전 해시를 함께 읽는 마이그레이션 기간을 예로 선택 방법을 구분합니다.


기본 후보 @Primary

새 회원가입은 현재 해시 구현을 사용하고, 기존 회원 로그인만 레거시 해시를 확인한다고 가정합니다.

@Bean
@Primary
PasswordHasher currentPasswordHasher() {
    return new Pbkdf2PasswordHasher(currentSettings);
}

@Bean
PasswordHasher legacyPasswordHasher() {
    return new LegacyPasswordHasher(legacySettings);
}

평범한 PasswordHasher 생성자 매개변수에는 @Primary가 붙은 현재 구현이 주입됩니다. @Primary는 “대부분의 주입 지점이 사용할 기본값”을 표현합니다.

의미를 지정하는 @Qualifier

레거시 회원을 검증하는 전용 객체는 선택 이유를 매개변수에 남깁니다.

@Bean
LegacyPasswordVerifier legacyPasswordVerifier(
        @Qualifier("legacyPasswordHasher")
        PasswordHasher passwordHasher
) {
    return new LegacyPasswordVerifier(passwordHasher);
}

문자열 오타가 걱정되면 사용자 정의 한정 애노테이션을 만들 수 있습니다.

@Target({FIELD, PARAMETER, METHOD})
@Retention(RUNTIME)
@Qualifier
public @interface LegacyHash { }

모든 후보 받기

마이그레이션 상태를 점검하는 관리 도구처럼 모든 구현이 필요한 경우 컬렉션을 주입합니다.

public final class PasswordHasherInventory {
    private final List<PasswordHasher> hashers;

    public PasswordHasherInventory(List<PasswordHasher> hashers) {
        this.hashers = List.copyOf(hashers);
    }

    public int implementationCount() {
        return hashers.size();
    }
}

List<PasswordHasher>는 후보가 여러 개인 상황을 의도적으로 받습니다. 반면 회원가입 서비스는 하나만 필요하므로 컬렉션을 받아 스스로 구현을 고르게 하지 않습니다.

선택 기준 비교

방법적합한 상황주의점
단일 타입후보가 정확히 하나둘이면 시작 실패
@Primary애플리케이션 기본 구현기본 의미가 분명해야 함
@Qualifier특정 역할·레거시 경로문자열보다 의미 이름 사용
List<T>모든 구현을 순회서비스가 임의 선택하지 않기

연습 문제

운영 프로필에서는 현재 해시만 회원가입 서비스에 주입하고, 마이그레이션 도구는 현재·레거시 구현을 모두 받게 구성하세요. @Primary를 제거했을 때 회원가입 서비스 생성이 실패하는지도 컨텍스트 테스트로 확인합니다.