아래는 해당 글의 설명을 돕기 위한 내가 작성한 코드이다.
SaveMemberUseCase 인터페이스
public interface SaveMemberUseCase {
void saveMember(SaveMemberRequestDTO saveMemberRequestDTO);
}
@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가지 방법이 있다.
@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 이름과 같은 빈이 있는지 찾아서 주입해준다.
@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 예외를 반환한다.
@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 어노테이션이 우선 순위를 가진다.
→ 스프링은 기본적으로 자동으로 수동으로 지정하는 것이 높은 우선 순위를 갖는다.