[SPRING] 의존관계 자동 주입
✔의존관계 주입 방법
의존관계 주입은 크게 4가지 방법이 있다.
- 생성자 주입
public class MyService {
private final MyRepository myRepository;
@AutoWired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
// ... rest of the class implementation
}
위 예시처럼 생성자에 @AutoWired
설정을 통해 의존관계를 주입한다.
생성자를 통해 의존관계를 주입할때 생성자 호출시점에 1번만 호출되는것을 보장하도록 만들며 이는 불편,필수 의존관계에 사용한다.(주로)
❗❗이때, 생성자가 1개있다면 @AutoWired 생략이 가능하다.
📌 4가지의 의존관계 주입중 생성자 주입을 사용하는것이 권장된다.
- 대부분의 의존관계 주입은 한번 발생하면 에플리케이션 종료 시점까지 의존관계를 번경할 일이없기때문에 (불변) 생성자 주입 사용을 권장한다.
- 수정자를 통해 의존관계를 주입한다면 test코드를 짤때 수정자를 통해 어떠한 값을 설정해야하는지 파악이 어렵기때문에(클래스에 들어가봐야 알수있다) 누락될수있다. 하지만 생성자 주입이라면 테스트 코드를짤때 객체생성시 생성자에 어떠한값을 넣을지 파악할수있기때문에 누락될일이 없다.
- 생성자 주입을 사용한다면
Final
을 사용할수있다. 이렇게되면 우리가 최기화 해야하는 필드를 초기화하지 않는 실수를 막을수있다. (생성자 주입만이 final 키워드 사용가능, 나머지 주입 방식은 모두 생성자 이후에 호출되기때문에 final 선언 불가!)
- 수정자 주입(setter 주입)
public class MyService {
private MyRepository myRepository;
@AutoWired
public void setMyRepository(MyRepository myRepository) {
this.myRepository = myRepository;
}
// ... rest of the class implementation
}
위 예시처럼 클래스에 존재하는 setter를통해 의존관계를 주입할수 있다. 이때는 @AutoWired
를 생략할수 없으며 이러한 방식은 선택,변경 가능성이 있는 의존관계에 사용한다.
@Autowired
는 주입할 대상이없을때(여기선 myRepository) 오류를 발생시키는데, 주입할 대상이 없더라도(myRepository) 동작하게 하려면 @Autowired(required = false)
로 지정하면 된다.
📌생성자 를 통한 의존관계 주입은 bean을 등록할때 같이 일어나지만 수정자 즉 setter로 의존관계를 주입할때는 bean등록 이후에 의존관계 설정이 된다.
- 필드 주입
public class MyService {
@Autowired
private MyRepository myRepository;
// ... rest of the class implementation
}
위 예시처럼 사용하면 필드를 통해 의존관계를 주입할수 있다. 하지만 필드를 통해 의존관계를 주입하는것은 권장되지않는다.
setter가 존재하지않는이상 외부에서 myRepository에 접근할수 있는 방법이 없기때문에 테스트하기 힘들다는 단점때문에 사용하지 않는다
- 일반 메서드 주입
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
위 예시처럼 일반 메서드에 @Autowired
를 붙여서 의존관계 주입을 해줄수도있다. 이렇게하면 한번에 여러필드를 주입받을수있지만 앞서말한 생성자주입이나 수정자주입을 통해 대체할수 있으므로 잘 사용하지 않는다.
✔옵션처리
주입할 스프링빈이 없더라도 @Autowired
가 동작하도록 만들수 있게 하려면 자동주입 대상으로 옵션처리를 하면된다.
- @Autowired(required=false)
- 자동 주입할 대상이 없다면 수정자 메서드 호출 자체가 안된다.
- org.springframework.lang.@Nullable
- 자동 주입할 대상이 없으면 null이 입력된다.
- 📌
@Nullable
optional은 스프링 전반에 걸쳐 사용할수가 있다.
- Optional<>
- 자동 주입할 대상이 없으면
Optional.empty
가 입력된다
- 자동 주입할 대상이 없으면
✔ lombok
롬복 라이브러리가 제공하는 @RequiredArgsConstructor 기능을 사용한다면 final로 선언한 필드에 대한 생성자를 자동적으로 만들어준다.
@RequiredArgsConstructor
public class Person {
private final String name;
private final int age;
private String address;
// constructor
public static void main(String[] args) {
Person person = new Person("kim",20);
}
}
이런식으로 생성자를 따로 만들지 않더라도 @RequiredArgsConstructor
를 통해 final 필드에 대한 생성자가 존재하는것을 볼수있다.
✔조회하는 bean이 2개 이상일때
@Autowired
를 사용하면 Type을 통해 bean을 조회하는 방식을 사용한다. 이때 타입이 같은 2개의 빈이 존재한다면 (이름은 다르지만) 오류가 발생하는데 이를 해결하는 방법은 다음과 같다.
- @Autowired 필드 명 매칭
@Autowired
private DiscountPolicy discountPolicy
FixDiscountPolicy, RateDiscountPolicy 클래스는 DiscountPolicy 인터페이스를 implement받는다. 즉 위와같이 적으면 type이 같은 bean이 2개이므로 오류가난다.
@Autowired
private DiscountPolicy rateDiscountPolicy
이때 위와같이 필드명을통해 우리가 사용할 bean의 이름을 명시해주면 오류를 제거해줄수 있다.
- @Qualifier
@Qualifier
를 통해 추가 구분자를 붙여줄수 있다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
위와같이 추가구분자를 붙여준후
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy){
...
}
생성자에 @Qualifier
를 사용하여 우리가 어떠한 bean을 사용할건지 명시해줄수 있다.
이때 @Qualifier
는 생성자 주입 뿐만아니라 수정자 주입 등에도 사용할수 있다.
- @Primary
- @Primary를 통해 위와같은 상황에서 RateDiscountPolicy, FixDiscountPolicy 2개가 존재할때
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Primary
를 지정해주면 우선적으로 RateDiscountPolicy을 사용할수 있다.
❗❗
@Primary
와@Qualifier
가 존재할때 우선권은@Qualifier
이 더 높다.Spring에서는 자동보다는 수동이, 넒은 범위의 선택권 보다는 좁은 범위의 선택권이 우선순위가 높다. 따라서 여기서도
@Qualifier
(세세한 설정이 가능한)의 우선순위가 더 높은것이다.
✔ Annotation 만들기
Annotation을 만들면 컴파일 단계에서 잡을수없는 @Qualifier("mainDiscountPolicy")
을 (문자를 넣을때 실수할수 있기때문에 ex) mmainDiscountPolicy) Annotation을 만들면 추후 문자열을 잘못 입력했을때 컴파일단계에서 오류를 잡을수 있다.
📌 조회한 빈이 모두 필요할때 List, Map
위의 예시에서 FixDiscountPolicy, RateDiscountPolicy 클래스는 DiscountPolicy 인터페이스를 implement받는다. 이때 DiscountPolicy 타입의 bean을 모두 조회하고싶다면 List,Map을 사용하면 된다.