저번 편에선 의존성 주입을 AppConfig와 같이 설정 클래스를 두고 관리하는 이유와 스프링 컨테이너에 넣어서 관리하는 이유 및 장점까지 살펴보았다. 그렇다면 마지막 코멘트에 있던 @Bean과 @Configuration을 반복적으로 넣어줘야 한다면 불편하지 않을까?
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(
memberRepository(),
discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy(){
return new FixDiscountPolicy();
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
저번 편에서 야기되었던 불편함을 없애고자 위 클래스를 제거하고 아래와 같이 AutoAppConfig를 생성해본다.
@Configuration
@ComponentScan
public class AutoAppConfig {
}
@ComponentScan이라는 새로운 어노테이션을 확인할 수 있다.
* @ComponentScan
@ComponentScan(컴포넌트 스캔)은 이름 그대로 @Component가 붙은 클래스를 스캔해서 스프링 빈으로 등록한다.
💡 참고로 @Configuration 내부에는 @Component가 있기때문에 자동으로 스캔의 대상이 된다.
@ComponentScan은 @Component가 붙은 클래스를 스캔하기 때문에 스프링 빈으로 사용할 클래스에 @Component를 붙여준다.
@Component
public class MemoryMemberRepository implements MemberRepository{}
@Component
public class RateDiscountPolicy implements DiscountPolicy{}
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
이제 빈으로 사용할 클래스들은 @Component 어노테이션을 붙임으로써 스캔의 대상이 되었고, 자동으로 빈으로 등록된다.
그런데 우리는 AppConfig와 같이 한 클래스내에서 의존관계를 설정하고 빈으로 관리했는데 이제 AutoAppConfig의 컴포넌트 스캔으로 각 클래스의 컴포넌트 별로 관리되기 때문에 한 클래스내에서 의존관계를 설정할수 없다. 따라서 의존관계 주입도 클래스 내에서 해결해야한다. 이와 관련된 어노테이션이 바로 위의 @Autowired이다.
@Autowired는 의존관계를 자동으로 주입해준다.
1. @ComponentScan (컴포넌트 스캔)
2. @Autowired 의존관계 자동 주입
그런데 만약 discountPolicy는 interface이고, discountPolicy는 rateDiscountPolicy 클래스와 fixDiscountPolicy클래스 두 가지 자식을 가지고 있다. memberRepository를 @Autowired하면 어떤것을 주입 해야할지 모르기 때문에 오류가나지 않을까?
Q.만약 조회 빈이 2개 이상이라면?
기본적으로 @Autowired는 타입으로 조회한다.
아래 상황을 예시로 보자.
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
@Component
public class RateDiscountPolicy implements DiscountPolicy {}
위 두가지를 모두 Bean으로 주입한뒤, 아래와 같이 의존관계 자동 주입을 실행하면?
@Autowired
private DiscountPolicy discountPolicy
기본적으로 @Autowired는 타입으로 조회하기 때문에 NoUniqueBeanDefinitionException 오류가 발생한다.
그렇다면, 조회 대상 빈이 2개 이상일때는 어떻게 해결해야 할까?
👉 조회 대상 빈이 2개 이상일 때 해결 방법
1.@Autowired 필드명 매칭
@Autowired
private DiscountPolicy discountPolicy
위 방법에서 아래와 같이 변경한다.
@Autowired
private DiscountPolicy rateDiscountPolicy
@Autowired가 먼저 타입 매칭을 시도하고 여러 빈이 있을경우 위와 같이 필드 명으로 매칭하여 주입한다.
따라서 위는 오류가 나지않고 정상주입된다.
2.Qualifier 사용
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
위와 같이 빈으로 사용할 클래스에 @Qualifier을 붙여주고
@Autowired
public OrderServiceImpl(@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
주입시에 @Qualifier를 붙여주고 등록한 이름을 적어준다.
💡Qualifier은 빈 이름을 변경하는것이 아니라, 추가 구분자를 붙여주는 방식이다. 만약 "mainDiscountPolicy"를 못찾으면 다음단계로 해당 이름의 스프링 빈을 추가로 찾는다. 팁이 있다면 @Qualifier로 명시되지 않은 스프링 빈을 주입받을때 @Qualifier를 사용하는것은 명확하지 않기 때문에 @Qualifier로 명시된 경우에만 사용하는것이 좋다.
3.@Primary 사용
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
@Primary는 우선순위를 정한다. 만약 @Autowired 사용시 여러 빈이 매칭되면 오류가 나지않고 @Primary가 우선권을 가지게된다.
💡만약 여러가지 빈이 매칭될수 있는 의존성을 주입할 경우 자주 사용하는 스프링 빈은 @Primary를 적용해서 사용하고, 특별한 기능으로 가끔 사용하는 스프링 빈은 @Qualifier를 지정해서 명시적으로 사용하면 코드를 깔끔하게 유지할 수 있다. 만약 이때 @Qualifier를 자주 사용하는 스프링 빈에 추가해도 상관은없다.
💡스프링은 자동보다는 수동이, 넓은 범위보단 좁은 범위의 선택권이 우선순위가 높기때문에 만약 같이 사용시 @Qualifier가 우선권이 높다.
모든 과정이 끝났다
의존성 주입을 하는 방식이 변경된 흐름에 따라 정리해보면 다음과같다.
* 총 정리
1.먼저 원시Java코드로 객체 생성시 추상화가 아닌 실질적인 대상을 new로 생성하여 OCP와 DIP를 위반한 코드를 먼저 살펴보았다. 모든 클래스에서 의존성이 필요할때 new로 생성하게 되면 나중에 의존성을 변경할때 new로 생성한 클래스들을 모두 변경해줘야 하며(OCP위반) 이는 곧 추상화가 아닌 구현체에 의존(DIP위반)하기 때문에 즉, 로미오는 상대가 누구든 상관없어야 하고 오로지 상대배역인 줄리엣 역할에 의존하여 공연을 해야하는데 위 경우는 로미오가 줄리엣을 직접 선택하여 공연을 하는것이나 다름없기때문에 이를 개선하기 위해 AppConfig라는 설정 클래스를 따로 만든뒤, 처음 한번만 설정하도록 변경하여 OCP와 DIP를 준수하였다.
2.한발짝 더 나아가 Spring프레임워크를 사용하여 스프링 컨테이너를 사용하였다. 각 의존 관계를 설정한 AppConfig클래스의 메서드를 모두 빈으로 등록하여 컨테이너가 관리하도록 하였다. 스프링 컨테이너로 관리하는 이유는 @Configuration을 사용하여 모든 빈이 싱글톤으로 유지되도록 설정할수 있고 컨테이너의 다양한 기능, 예를들어 다국어지원 메시지처리등의 기능을 함께 제공받기 때문에 효율적으로 사용할 수 있기때문이다.
3.모든 메서드에 @Bean을 일일이 넣어주는 불편함이 생겼다. 따라서 컴포넌트 스캔을 사용하여 빈으로 관리될 대상을 컴포넌트로 설정했기에 불편함을 덜어주었다. 그러나 컴포넌트는 클래스별로 관리되기 때문에 AppConfig같이 한 클래스내에서 의존관계를 설정할수 없었다. 따라서 @Autowired를 사용하여 클래스내에서 의존관계를 쉽게 주입할 수 있었다. 이때 자동으로 등록하여도 OCP와 DIP는 유지한채로 등록되기에 맘편히 등록할수 있었다. 왜냐하면 부모 타입을 주입시켜도 문제가 없도록 @Primary와 @Qualifier같은 어노테이션으로 추상화를 주입받을수 있기 때문이다.
💡자료 및 강의 출처
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard
'Spring' 카테고리의 다른 글
Spring MVC 정리 3) 검증 (0) | 2023.11.06 |
---|---|
Spring MVC 정리 2)단계별로 구현하며 알아보는 스프링의 핵심 기술 (0) | 2023.10.25 |
Spring MVC 정리 1)웹 어플리케이션의 이해 (0) | 2023.10.20 |
빈의 생명주기와 스코프 , DL(의존성 검색) (0) | 2023.09.22 |
의존성 주입으로 살펴보는 스프링 컨테이너와 싱글톤 컨테이너 (0) | 2023.09.12 |