이전 글에서부터 저희는 Configuration 클래스가 자동으로 구성되도록 하고 있습니다.
Component Scan을 통해서도 우선 어느 정도 구성이 되었지만 추가적으로 의존관계에 대해선 적용한 것이 없기 때문에 요건 Autowired라는 기능을 써서 자동으로 구성되게 만들어볼까 합니다.
이렇게 OrderServiceImpl은 생성자를 가지고 AppConfig는 위치에 맞게 의존할 클래스를 넣어주었습니다.
자동으로 설정할 Autowired 같은 경우에는 아예 생성자를 작성한 곳으로 타고 갑니다! 저희 프로젝트에선 MemberServiceImpl, OrderServiceImpl이겠죠? 그리고 그 생성자에 @Autowired라는 어노테이션을 추가해 봅시다. 그럼 준비는 끝입니다.
(MemberServiceImpl의 생성자에도 추가해 주세요!)
그리고 위에 있는 필드들에 final 키워드를 붙여줘서 생성자에 혹시라도 값이 설정되지 않는 문제를 컴파일 시점에서 막아줄 수 있도록 합시다.
(MemberServiceImpl의 필드에도 추가해 주세요!)
이렇게 Autowired를 추가해 주면 저희가 추가한 스프링의 Bean들 중 해당 클래스 타입과 일치한 빈을 찾습니다. 그리고선 의존관계를 자동으로 주입하여 주는 역할을 해 줍니다.
의존관계 설정이 거의 다 완료되었습니다! 하지만 추가적으로 조금 손봐줄 곳이 있는데요. 그리고 Autowired가 해줄 수 있는 것들이 더 무엇이 있는지도 알아보겠습니다.
Autowired 옵션 설정
의존관계 주입을 위해 우선 Bean들 중에서 일치하는 클래스의 Bean을 찾는다고 했는데 만약 일치하는 게 없다면 어떻게 될까요?? 그럼 바로 오류가 발생합니다.
근데 오류 굳이 안 내고 쌈뽕 하게 못 찾아도 넘어가야 할 때도 있지 않겠습니까? 그럴 땐 3가지의 방법으로 오류 없이 의존성 주입 안 하고 스무스하게 넘어가는 방법이 있습니다. OrderServiceImpl에서 예를 들어 확인해 봅시다.
1. 그냥 없던 걸로 해줄래..?
@Autowired에는 required라는 옵션이 있습니다. 기본적으로는 true로 기본값이 되어있어 꼭 매칭이 되어야만 되도록 설정이 되어 있기 때문에 못 찾는 일이 발생하면 오류가 나게 되는 겁니다.
그렇다면 required를 false로 변경해 버리죠!
이렇게 되면 그냥 이 생성자 자체가 없던 일이 됩니다.
2. null 줄 테니까 넘어가!
아예 없던 일로 하기에는 또 너무한 것 같나요? 그럼 못 찾은 친구들에게 null을 주고 넘어가는 방법도 있습니다.
@Nullable을 null을 넣어도 되는 녀석들 앞에 넣어주세요!
이렇게 된다면 만약 MemberRepository타입 혹은 DiscountPolicy타입의 스프링 빈을 찾지 못하면 의존관계 연결을 넘어가고 해당 변수에 null이 들어간 채 넘어가게 됩니다.
3. Optional을 써서 넘겨주기
Optional이라는 자바 기능 아시나요? 이를 이용해 Optional.empty라는 값으로 넘겨주는 방법도 있답니다. Nullable을 쓰는 방식 그리고 이 Optional을 쓰는 방식은 찾아야 하는 빈 중 일부만을 적용시킬 수도 있습니다.
위와 같은 상황이라면 memberRepository는 해당하는 타입의 빈을 찾지 못하면 Optional.empty라는 값을 반환하여 넘어갈 수 있게 됩니다. 하지만 Nullable, Optional과 같은 방식을 쓰지 않은 discountPolicy를 찾지 못하면 얄짤없이 에러가 나게 됩니다.
이와 같은 옵션들을 적절하게 써서 현재 내 클래스에서 필요할 의존 주입 상황을 지정해 봅시다! 저희는 다시 태초의 required=true, Nullable, Optional를 하지 않은 상태로 돌아옵시다.
조회된 빈이 2개 이상일 때
저희에게 필요했던 손봐줄 부분이 바로 요거였습니다. OrderServiceImpl을 다시 봐볼까요?
@Autowired를 통해 지금 찾아올 것은 두 가지죠? 바로 MemberRepository 타입의 빈과 DiscountPolicy 타입의 빈입니다.
저희가 @Component를 통해 등록했던 빈들을 떠올려보면 MemberRepository타입은 MemoryMemberRepository 하나, DiscountPolicy 타입은 FixDiscountPolicy, RateDiscountPolicy 두 개입니다.
이렇게 조회해 보니 빈의 개수가 2개 이상이라면 우리는 저 생성자의 discountPolicy에 어떤 할인정책 클래스가 들어가게 될지 모르겠죠?
저희도 모르는데 컴퓨터야 알겠습니까? 컴퓨터도 모르니까 조회된 빈이 두 개 이상이면 에러를 때려버립니다.
NoUniqueBeanDefinitionException 에러라는 게 뜨는데 이때는 아래와 같은 작업을 통해 우선순위를 설정하거나 명확하게 지정할 수 있습니다.
1. 파라미터 이름을 원하는 빈의 이름으로!
바로 보여드리죠.
이렇게 하게 된다면 파라미터의 이름에 맞게 매칭이 됩니다. 근데 이렇게 되면 OrderService 내에 rateDiscountPolicy를 쓴다고 못을 박아버리는 거라 의존도가 너무 올라가 버리겠죠?
저희는 이런 걸 안 하려고 열심히 객체지향! 객체지향! 한 거기 때문에 다른 방법도 알아보겠습니다.
뜻 그대로 얘랑 겹치는 빈들이 있으면 얘를 우선으로 꺼내쓰라는 뜻입니다. 우리가 비율 할인 정책을 적용하려면 @Primary를 그럼 어디에다가 써줘야 할까요? 맞습니다 바로 RateDiscountPolicy 클래스 자체입니다. 이렇게 되면 OrderServiceImpl에 의존성을 높이지도 않겠죠?
물론 @Primary도 좋지만 일부분에는 @Primary가 아닌 다른 특정한 빈을 써야 할 때도 있겠죠? 그때를 위해 @Qualifier가 있습니다.
@Qualifier는 빈의 이름을 정하는 건 아니고 의존성 주입을 위해 빈을 찾을 때 해당 빈의 별칭을 정해준다라고 생각해 주시면 됩니다.
저희는 주로 비율할인을 메인으로 사용하기에 @Primary를 부여하겠지만 어떤 부분에선 정률할인을 서브로 사용하기 때문에 @Qualifier로 subDiscountPolicy라는 별칭을 준다고 생각해 봅시다.
그리고 저희의 OrderService가 Primary로 사용하는 비율할인정책이 아닌 정률할인정책을 사용한다 하면 아래와 같이 불러와주면 됩니다.
지금은 다시 기존 OrderServiceImpl로 돌려놓읍시다.
테스트를 통해 확인하기
저희가 지금까지 한 게 잘 작동하는지 한번 테스트해 볼까요?
아래와 같이 autoScan 패키지와 autoScanTest라는 클래스를 테스트 쪽에 만들어봅시다.
빈들이 잘 조회되는지부터 확인해 볼까요? @Component를 붙여놓은 빈들이 다 나오고 있는지 조회해 봅시다.
실행결과
저희가 등록했던 빈들은 다 찾아와 지고 있는 것 같습니다.
추가적으로 Test 클래스에 저희의 임의의 주문 서비스 두 개를 추가해 봅시다.
보시다시피 저희의 OrderServiceImpl과 아주 동일합니다. 차이점이라면 orderService2에는 @Qualifier를 이용하여 subDiscountPolicy라고 지정한 빈을 찾고 있는 것입니다.
이 두 서비스를 비교해 보도록 하겠습니다.
실행 결과
임의의 사용자를 가입시킨 후 같은 가격의 물건을 주문하도록 했습니다.
primaryServiceOrder는 Primary 즉 RateDiscountPolicy를 적용한 OrderService에서 주문이 되어서 discountPrice가 2000이 된 것을 볼 수 있습니다.
그리고 qualifierServiceOrder는 Qualifier(“subDiscountPolicy”)를 적용한 OrderService에서 주문이 되어서 discountPrice가 고정금액인 1000원이 된 것을 확인하실 수 있습니다.
Qualifier를 어노테이션으로 만들기
Qualifier는 인자값으로 String으로 된 별칭을 받습니다. 그러나 문자열이라는 게 코드로 작성하다 보면 오타가 나도 잘 모르게 되지 않겠습니까? 그래서 안전하게 어노테이션을 따로 하나 만드는 것을 추천드립니다.
저희 discount 패키지에 새 클래스를 추가하고 어노테이션으로 추가해 볼까요?
그리고 위에 @Qualifier(“subDiscountPolicy”)와 함께 필요한 여러 어노테이션들을 추가시켜 주면서 완성시켜 줍시다.
저 위에 들어가는 것들은 저도 잘 몰라서 복붙하고 있습니다..
그럼 이제 이 이후로 문자열로 subDiscountPolicy를 사용할 일은 사라졌습니다.
FixDiscountPolicy 클래스로 가서 우리가 방금 만든 어노테이션을 추가해 줍시다.
그리고 이걸 사용할 때도 같은 어노테이션을 주면 됩니다. 방금 만든 테스트를 위한 클래스 중 OrderService2의 생성자를 아래와 같이 변경해 봅시다.
그 후 테스트를 진행해도 FixDiscountPolicy로 잘 찾아오는 것을 확인하실 수 있습니다.
마치며
오늘 그리고 지난 시간에 정말 잘만 배워두면 엄청나게 유용할 자동으로 클래스들을 찾아와 빈으로 등록하고, 그 빈의 의존관계까지 자동으로 주입하는 기능을 배웠습니다.
저는 이게 진짜 엄청 유용할 기능일 것이라고 생각됩니다!! 빈 설정만 잘해도 반은 먹고 들어가지 않을까 합니다ㅋㅋㅋㅋ
다음으로는 빈 생명주기를 배워볼 건데, 저도 배워야 해서 시간이 조금 걸릴 수도 있습니다. 열심히 배워서 오겠습니다!