Spring은 어떻게 구현체를 빈으로 등록할까?

Kevin·2024년 1월 3일
0

Spring

목록 보기
1/11
post-thumbnail

아래는 해당 글의 설명을 돕기 위한 내가 작성한 코드이다.

SaveMemberUseCase 인터페이스

public interface SaveMemberUseCase {
    void saveMember(SaveMemberRequestDTO saveMemberRequestDTO);
}

*SaveMemberUseCase 인터페이스를 구현한 MemberService*
@Service
@RequiredArgsConstructor
public class MemberService implements SaveMemberUseCase {

    @Override
    public void saveMember(SaveMemberRequestDTO saveMemberRequestDTO) {
        saveMemberPort.saveMember(memberMapper.toEntity(saveMemberRequestDTO));
    }
}

SaveMemberUseCase 인터페이스를 구현한 MemberSaveService

@Service
@RequiredArgsConstructor
public class MemberSaveService implements SaveMemberUseCase {

    @Override
    public void saveMember(SaveMemberRequestDTO saveMemberRequestDTO) {
        saveMemberPort.saveMember(memberMapper.toEntity(saveMemberRequestDTO));
    }
}

SaveMemberUseCase를 의존하는 MemberController

@RestController
@RequestMapping("/api/member")
@RequiredArgsConstructor
public class MemberController {

    private final SaveMemberUseCase saveMemberUseCase;

    // 회원가입
    @PostMapping("")
    void saveMember(@RequestBody SaveMemberRequestDTO requestDTO){
        saveMemberUseCase.saveMember(requestDTO);
    }
}

만약 하나의 인퍼페이스를 구현한 2개 이상의 구현체들이 존재하고, 이 구현체들이 모두 빈으로 등록이 되어있다면, 스프링은 어떤 구현체를 DI(주입)해줄 까?

정답은 아무 설정 없이는 아래와 같은 에러가 발생한다.

NoUniqueBeanDefinitionException: No qualifying bean of type 'test.a' available: expected single matching bean but found 2


위 에러의 뜻은 1개의 빈만 매칭이 되어야 하지만 현재 2개 or n개의 빈이 매칭이 되었다는 것을 의미한다.

Spring boot가 어떤 빈을 DI(주입)해야하는지 이 경우에는 반드시 알려주어야 한다.

그러면 어떻게 알려주느냐?

아래와 같은 3가지 방법이 있다.


1. Autowired된 field의 이름을 빈 이름으로 설정하는 방법

@RestController
@RequestMapping("/api/member")
public class MemberController {

    private final SaveMemberUseCase ***MemberService***;

    // 회원가입
    @PostMapping("")
    void saveMember(@RequestBody SaveMemberRequestDTO requestDTO){
        saveMemberUseCase.saveMember(requestDTO);
    }

		@Autowired
		public MemberController(SaveMemberUsecase saveMemberUsecase) {
			this.memberService = saveMemberUsecase;
		}
}

→ Autowired로 생성자에 주입되는 SaveMemberUseCase 객체의 변수명을 구현체 클래스의 이름인 MemberService로 만들면, SaveMemberUseCase 클래스의 빈이 두 개인 것을 확인한 Spring Boot가 MemberService 이름과 같은 빈이 있는지 찾아서 주입해준다.



2. @Qualifier 어노테이션을 이용하는 방법

@Qualifier("member")
@Service
@RequiredArgsConstructor
public class MemberService implements SaveMemberUseCase {

    @Override
    public void saveMember(SaveMemberRequestDTO saveMemberRequestDTO) {
        saveMemberPort.saveMember(memberMapper.toEntity(saveMemberRequestDTO));
    }
}
@Qualifier("memberSave")
@Service
@RequiredArgsConstructor
public class MemberSaveService implements SaveMemberUseCase {

    @Override
    public void saveMember(SaveMemberRequestDTO saveMemberRequestDTO) {
        saveMemberPort.saveMember(memberMapper.toEntity(saveMemberRequestDTO));
    }
}
@RestController
@RequestMapping("/api/member")
public class MemberController {

    private final SaveMemberUseCase saveMemberUseCase;

    // 회원가입
    @PostMapping("")
    void saveMember(@RequestBody SaveMemberRequestDTO requestDTO){
        saveMemberUseCase.saveMember(requestDTO);
    }

		@Autowired
		public MemberController(@Qualifier("member") SaveMemberUsecase saveMemberUsecase) {
			this.saveMemberUseCase = saveMemberUsecase;
		}
}

Qualifier 어노테이션은 빈에 추가 구분자를 붙여주는 방법으로 생성자에서 해당 구분자를 명시하면 그 구분자를 가진 빈을 주입해준다.

→ 이는 빈 이름을 변경하는게 아니라 추가적인 구분자를 두는 것이다.

→ 그렇기에 만약 member라는 구분자를 가진 스프링 빈이 없으면, 해당 이름을 가진 member라는 빈을 찾아다닌다. 그러다가 해당 이름을 가진 빈도 없으면 NoSuchBeanDefinitionException 예외를 반환한다.


3. @Primary 어노테이션을 이용하는 방법

@Primary
@Service
@RequiredArgsConstructor
public class MemberService implements SaveMemberUseCase {

    @Override
    public void saveMember(SaveMemberRequestDTO saveMemberRequestDTO) {
        saveMemberPort.saveMember(memberMapper.toEntity(saveMemberRequestDTO));
    }
}
@Service
@RequiredArgsConstructor
public class MemberSaveService implements SaveMemberUseCase {

    @Override
    public void saveMember(SaveMemberRequestDTO saveMemberRequestDTO) {
        saveMemberPort.saveMember(memberMapper.toEntity(saveMemberRequestDTO));
    }
}
@RestController
@RequestMapping("/api/member")
public class MemberController {

    private final SaveMemberUseCase saveMemberUseCase;

    // 회원가입
    @PostMapping("")
    void saveMember(@RequestBody SaveMemberRequestDTO requestDTO){
        saveMemberUseCase.saveMember(requestDTO);
    }

		@Autowired
		public MemberController(SaveMemberUsecase saveMemberUsecase) {
			this.saveMemberUseCase = saveMemberUsecase;
		}
}

Qualifier 어노테이션은 모든 구현체에 붙여줘야 하기에 상대적으로 불편하다.

그러나 Qualifier 어노테이션과 Primary 어노테이션을 같이 사용하면 시너지를 낼 수 있다.

메인 기능의 빈에는 Primary를 적용하고, 서브 기능으로 사용하는 빈에는 Qualifier를 적용하면 두 장점을 각각 사용할 수 있다.

→ 이 때 우선순위는 Primary보다 명시적으로 지정하는 Qualifier 어노테이션이 우선 순위를 가진다.

→ 스프링은 기본적으로 자동으로 수동으로 지정하는 것이 높은 우선 순위를 갖는다.

레퍼런스

@Qualifier와 @Primary 어노테이션 사용법

profile
Hello, World! \n

0개의 댓글