-
Spring(4) - 의존 관계 주입, DI(Dependency Injection)Back-end/Spring 2021. 7. 11. 22:13반응형
오늘은 DI에 대해서 포스팅하겠다.
DI란 개념이 진짜 처음에는 와닿지 않았다. 어떻게 컨테이너가 주입을 해줄까?
그리고 왜 DI를 사용할까라는 생각이 계속 들었다. 그렇게 계속 Spring을 사용하여 코드를 짜니 이해가 됬다.
DI에 대한 나의 생각을 정리해보겠다.
일단 DI(Dependency Injection)은 뭘까?
바로 우리가 @ApplicationContext로 컨테이너를 생성하고 @Component로 Bean을 등록하여 @Autowired를 통해 의존 관계를 주입시키는 것이다.
처음에 이해가 안 될 것이다. 아래의 그림을 참고하자.
위의 그림과 같이 Bean을 등록하면 DI 컨테이너가 Bean들을 관리한다.
그럼 어떻게 Bean을 이용해서 주입할까?
일단 쉽게 생각해서 Service나 Repository를 변수라고 생각해보자. 실질적으론 객체이다.
일단 객체들을 선언해보자. 그리고 이러한 의존 관계 주입은 Bean내에서만 일어나는 것이다.
그래서 Component-Scan이 가능하도록 @Component를 클래스에 붙여준다.
//이 코드는 그냥 클래스마다 객체를 새로 생성하는 코드이다. //private final MemberRepository memberRepository = new MemoryMeberRepository(); //private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); //의존관계 주입을 위해서 객체선언 방법1 : 생성자 주입 private final MemberRepository memberRepository; private final DiscountPolicy discountPolicy; @Autowired public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; } //의존관계 주입을 위해서 객체선언 방법2 : setter 주입 private MemberRepository memberRepository; private DiscountPolicy discountPolicy; @Autowired public void setMemberRepository(MemberRepository memberRepository) { this.memberRepository = memberRepository; } @Autowired public void setDiscountPolicy(DiscountPolicy discountPolicy) { this.discountPolicy = discountPolicy; } //의존관계 주입을 위해서 객체선언 방법3 : 필드 주입 @Autowired private MemberRepository memberRepository; @Autowired private DiscountPolicy discountPolicy;
이렇게 크게 3가지 경우로 의존 관계 주입이 가능하다.
그러나 우리는 대부분 생성자 주입으로 의존 관계 주입을 시도한다. 왜 그런 것일까?
일단 Setter주입부터 보자. Setter주입을 사용할 경우는 set메소드를 사용하기 위해 public으로 메소드로 열어놔야한다.
그러나 의존 관계 주입은 단 한 번만 일어나고 그 다음엔 불변상태로 있어야하기 때문에 Setter는 적당하지 않다.
그 다음 필드주입을 보자. 필드 주입은 코드가 매우 간결해지고 깔끔해서 좋다.
그러나 현재 테스트 코드의 중요성이 부각되면서 사용이 불가하다. 왜냐하면 Test코드를 실행 할 경우 모든 컴포넌트들을 스캔하지 않기 때문에 NullPointException이 발생한다. 주입을 못해준다는 얘기이다.
이러한 이유로 우리는 생성자 주입을 사용해야한다.
더 나아가서 Lombok이라는 라이브러리를 사용 할 경우 생성자 코드 생성 및 객체의 게터, 세터를 자동으로 생성해준다.
아래의 코드를 참고하자.
@Component @RequiredArgsConstructor public class OrderServiceImpl implements OrderService { private final MemberRepository memberRepository; private final DiscountPolicy discountPolicy; } /** ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ **/ public class Member { private Long id; private String name; }
그럼 여기서 문제가 발생 할 수도 있다. 일단 기본적으로 @Autowired는 Type형태로 컴포넌트 스캔을 한다.
@Autowired private DiscountPolicy discountPolicy
그럼 이와 같이 의존 관계 주입을 사용하면 DiscountPolicy라는 Type의 객체를 참조 할 것이다.
그러면 저 타입의 클래스가 2개 존재한다고 가정하자.(FixDiscountPolicy,RateDiscountPolicy)
그럼 다음과 같은 오류가 발생 할 것이다.(NoUniqueBeanDefinitionException)
어떻게 해결하는게 좋을까?
방법은 3가지가 있다. 아래의 코드를 참조하자.
//조회 빈이 2개 이상 일 경우 의존 관계 주입여부 결정 1 : @Autowired 필드 명 private DiscountPolicy rateDiscountPolicy //조회 빈이 2개 이상 일 경우 의존 관계 주입여부 결정 2 : @Qualifier 사용 @Component @Qualifier("mainDiscountPolicy") public class RateDiscountPolicy implements DiscountPolicy {} ..... @Autowired public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; } //조회 빈이 2개 이상 일 경우 의존 관계 주입여부 결정 2 : @Primary 사용 @Component @Primary public class RateDiscountPolicy implements DiscountPolicy {}
이렇게 3가지 방법이 있다. @Qualifier와 @Primary가 섞여있을 경우에는 @Qualifier가 우선순위를 가지게 된다.
이렇게 활용해서 사용하면 된다.
마지막으로 빈을 조회할 때 2개가 다 필요한 경우가 있다.
빈 조회 시 없을 경우에는 @Autowired(required = false)와 @Nullable 애노테이션으로 처리해주면 된다.
조회 할 빈이 다 필요한 경우는 사용자가 DiscountPolicy를 직접 정하고 싶은 경우에는 우리가 제공을 해줘야한다.
이 때 사용하는게 Map과 List이다.
static class DiscountService { private final Map<String, DiscountPolicy> policyMap; private final List<DiscountPolicy> policies; public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) { this.policyMap = policyMap; this.policies = policies; } public int discount(Member member, int price, String discountCode) { DiscountPolicy discountPolicy = policyMap.get(discountCode); return discountPolicy.discount(member, price); } }
이 코드는 처음에 생성자 주입으로 2개의 Bean이 조회 될 것이다.
그리고 이것을 Map에 String형태로 저장을 한다. Map은 <key, value>로 구성되어 저장해주고 List같은 경우에는 그냥 배열 같이 저장해준다. 둘 중 하나를 선택해서 사용하면 된다.
이렇게 DI(Dependency Injection)에 대해서 알아봤다. 진짜 객체의 특성을 잘 살려주는 특성이다.
이러한 DI를 이용하여 엄청나게 유연한 코딩을 할 수 있겠다라는 생각이 든다. 필요할 때 모든 것을 고치지 않고 그 객체가 가진 역할을 대체 시키면서 코드를 짜서 주입 시켜 줄 수 있다는게 큰 매력이다.
이렇게 Spring에 대해서 공부를 하면 할수록 매력적인 프레임워크라는게 느껴지고 재미가 있다.
반응형'Back-end > Spring' 카테고리의 다른 글
Spring(6) - Mock, Mockito (0) 2021.08.25 Spring(5) - @Transactional (0) 2021.08.18 Spring(3) - Bean등록하는 방법 (0) 2021.06.24 Spring(2) - Junit Test과 Spring Test (0) 2021.06.24 Spring(1) - Ioc와 DI컨테이너 작성 (0) 2021.06.24