본문으로 건너뛰기

안동민 개발노트

본문 시작

의존성 후보 선택

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

같은 타입의 빈이 둘 이상이면 컨테이너는 하나를 임의로 고르지 않습니다.

게시판이 새 비밀번호 해시와 이전 해시를 함께 읽는 마이그레이션 기간을 예로 선택 방법을 구분합니다.

복수 정책은 선택 근거가 없을 때만 오류다

Completion과 FocusTime 모두 유효하지만 plain PasswordHasher 하나를 요구하면 후보 집합을 줄일 수 없다.

  1. PasswordHasher 요청

    단일 의존성 계약

  2. Completion 후보

    고정 10점 구현

  3. FocusTime 후보

    시간 비례 구현

  4. 모호성

    NoUniqueBeanDefinitionException


기본 후보 @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 { }
Primary와 Qualifier는 다른 질문에 답한다

대부분이 쓰는 기본값과 특정 역할이 요구하는 구현을 같은 표식으로 해결하지 않는다.

  1. plain 주입인가

    Primary가 기본 후보

  2. 집중 분석 역할인가

    focus-time Qualifier로 지정

  3. 요청마다 바뀌는가

    전체 후보를 registry로 전달

  4. 모두 실행하는가

    ordered collection을 순회


모든 후보 받기

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

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>는 후보가 여러 개인 상황을 의도적으로 받습니다.

반면 회원가입 서비스는 하나만 필요하므로 컬렉션을 받아 스스로 구현을 고르게 하지 않습니다.

semantic qualifier는 빈 이름과 업무 의미를 분리한다

factory method 이름이 바뀌어도 focus-time이라는 주입 계약은 custom annotation에 남는다.

  1. PolicyMode

    Qualifier를 확장한 업무 annotation

  2. candidate

    focus-time 값을 구현에 표시

  3. injection point

    같은 값을 요구해 후보를 축소

  4. refactor

    빈 method 이름 변경과 독립


선택 기준 비교

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

기본 하나·고정 역할 하나·runtime 전체 선택은 서로 다른 객체 graph를 만든다.

요구도구결정 시점
기본 후보 하나Primarycontext 조립
특정 역할 하나Qualifierinjection point 해결
요청 key 선택List와 registry업무 호출

연습 문제

운영 프로필에서는 현재 해시만 회원가입 서비스에 주입하고, 마이그레이션 도구는 현재·레거시 구현을 모두 받게 구성하세요.

@Primary를 제거했을 때 회원가입 서비스 생성이 실패하는지도 컨텍스트 테스트로 확인합니다.