7장 의존관계 자동주입
인프런 김영한님의 스프링 핵심 원리 - 기본편 7장 의존관계 자동주입 정리했으며 본인이 핵심이라고 생각하지 않은 부분에 대해서는 과감하게 생략하였습니다.
다양한 의종관계 주입 방법
- 생성자 주입
- 수정자 주입(Setter)
- 필드 주입
- 일반 메서드 주입
생성자 주입
이름 그대로 생성자를 통한 의존 관계 주입
특징
- 생성자 호출시점에 딱 1번만 호출되는 것이 보장
- 불변, 필수 의존관계에 사용
- 생성자가 실행중에는 아직 클래스의 멤버 변수들이 초기화 되지 않은 시점이기 때문에
final
키워드가 붙은 멤버 변수에 대하여 의존성 주입이 가능
사용 예시
- 일반적인 사용
@Component public class OrderServiceImpl implements OrderService { private MemberRepository memberRepository; private DiscountPolicy discountPolicy; @Autowired public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; } }
Tip : 클래스 생성자가 하나뿐이라면
@Autowired
는 생략 가능합니다.
- 롬북(lombok)을 활용한 사용 (추천)
@Component @RequiredArgsConstructor public class OrderServiceImpl implements OrderService { private final MemberRepository memberRepository; private final DiscountPolicy discountPolicy; }
룸북을 이용하면 final 키워드가 붙은 필수 초기화 멤버 변수들에 대하여 의존성 주입이 가능한 생성자를 자동으로 만들어줍니다. 코드가 깔끔하기 때문에 이 방식을 추천합니다.
옵션(주입의 필수 여부) 처리
말 그대로 주입의 필수 여부 선택이 가능합니다.
만약 원하는 빈
이 존재하지않아 주입이 불가능한 상황이라면 이를 그냥 진행할지, 또는 진행하면 안될지를 정할 수 있습니다.
@Autowired
만 사용하면 required 옵션의 기본값이 true 로 되어 있어서 자동 주입 대상이 없으면 오류가 발생합니다.
- @Autowired(required=false) 자동 주입할 대상이 없으면 메서드 자체가 호출되지 않습니다.
//호출 안됨@Autowired(required = false)
public void setNoBean1(Member member) {
System.out.println("setNoBean1 = " + member);
}
- org.springframework.lang.@Nullable 자동 주입할 대상이 없으면 null이 입력됩니다.
//null 호출
@Autowired
public void setNoBean2(@Nullable Member member) {
System.out.println("setNoBean2 = " + member);
}
- Optional<> 자동 주입할 대상이 없으면 Optional.empty 가 입력됩니다.
//Optional.empty 호출
@Autowired(required = false)
public void setNoBean3(Optional<Member> member) {
System.out.println("setNoBean3 = " + member);
}
### Optional<>
여기서 Optional<> 생소하실수 있어서 잠깐 설명을 드리자면 자바8 부터 사용할 수 있는 클래스이며, 런타임중에 의도치않게 일어나는 NullPointerException 을 최대한 줄이기 위해서 개발되었습니다.
우리가 개발을 하다보면 NullPointerException 꼭 한번은 존재하는데, 이 객체가 언제 Null이 된 것인지 확인을 하고자 디버깅하는 시간이 생각보다 많이 소요됩니다.
왜냐면 객체를 Set 하고나서 바로 사용하지않고 한참 뒤에 사용하면 콜스택을 추적하면서 이게 언제 Null로 변했을까~~ 하고 스크롤을 올려보면서 찾거나 열심히 Ctrl + F를 하여 Null이 되어버린 객체 =
를 검색해야하죠,
이를 근본적으로 해결하기 위한 방법은 간단합니다. 객체의 값이 바뀌는 시점에 객체가 Null이면 바로 예외를 발생시켜버리는 것 입니다.
- Optional 사용 전 ```java
User myUser = userService.findUser(“wusub”); // findUser() 의 반환값이 null 일 때
// 기타 부가적인 로직.. // 기타 부가적인 로직..
User tempUser = myUser;
// 기타 부가적인 로직.. // 기타 부가적인 로직..
tempUser.getName(); // NullPointerException 발생!
`myUser`객체가 실제로 `null` 이 된 시점은 `myUser.getName();` 가 호출되기 한참 전인 `userService.findUser("wusub");` 호출 시점이며 이미 `myUser`객체가 null이 된 시점에서 해당 로직은 실패가 예정되어있는데 기타 부가적인 로직을 진행하여 불필요한 리소스가 소모된다는 점, 그리고 로직이 섞여있어 `myUser` 가 null이된 실제 시점을 찾기에 불편하다는 점이 존재합니다.
2. **Optional 사용 후**
```java
Optional<User> myUser = Optional.of(userService.findUser("wusub")); // findUser() 의 반환값이 null 일 때 바로 NullPointerException 발생!
Optional을 사용하면 userService.findUser("wusub")
의 반환이 null이 시점에서 바로 NullPointerException
이 발생합니다.
불필요한 기타 부가적인 로직은 진행되지도 않고 콜스택도 Optional.of(userService.findUser("wusub"));
에서 멈추게 되기에 리소스 낭비가 없고 디버깅하기 훨씬 수월합니다.
그렇다고 Optional을 남발하면 만사 오케이냐? 그건 또 아닙니다. Optional 객체는 내부적으로 null 검증
이라는 작업이 진행되는데 모든 변수에대하여 null 검증
로직은 생각보다 리소스가 많이 소모됩니다.
해당 포스팅은 Optional 에 대한 내용이 아니기에 여기까지만 설명하며, 추후에 Optional 에 대해서는 별도의 포스팅을 작성해보겠습니다.
다시 본론으로 돌아가
//Optional.empty 호출
@Autowired(required = false)
public void setNoBean3(Optional<Member> member) {
System.out.println("setNoBean3 = " + member);
}
위 코드에서 Optional.empty
라는 말은 null은 아니고 비어있는 객체, 사용자가 null이 아닌 다른 방법으로 객체가 비어있는것을 체크할 수 있도록 하기 위함입니다.
조회 빈이 2개 이상
Spring이 자동으로 의존성 주입을 시도하려하는데, 가져와야할 Bean이 2개일 경우 어떻게될까요
실제로 Bean 이름으로 조회시는 위 현상이 발생할 경우가 드물지만 타입
으로 빈을 조회하게될 경우 선택지가 2개 이상일 가능성이 존재합니다.
특히 @Autowired
는 기본이 Type
으로 조회이기 때문에 하위 타입
을 갖는 빈을 주입시 선택지가 2개 이상이 되어 스프링이 선택을 하지못하고 에러가 발생하는 경우가 있습니다.
예를 들어
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
위와 같이 DiscountPolicy
를 구현하는 rateDiscountPolicy, fixDiscountPolicy 라는 두개의 Bean으 등록되어 있을 때
아래와 같이 상위 타입인 DiscountPolicy
를 @Autowired
를 주입받으려하면 에러가 발생합니다.
@Autowired
private DiscountPolicy discountPolicy
이때 대표적인 해결법 3가지가 존재합니다.
1. Bean 이름과 변수명 매칭
쉽습니다. 내가 주입 받고자 하는 bean이름과 @Autowired
가 선언된 변수 이름을 매칭 시키는 것 입니다.
@Autowired
를 통한 빈 주입 순서는 타입
-> 빈 이름과 동일 여부
or 파라미터 이름 (생성자 또는 셋터 주입일 경우)
@Autowired
private DiscountPolicy rateDiscountPolicy
@Autowired
private DiscountPolicy fixDiscountPolicy
2. @Qualifier (내용 식별 수식자)
빈이 등록되는 객체에 수식어
를 달아서 구분할 수 있게 하는 것 입니다.
이는 빈 이름과는 별도의 메타 정보
입니다.
사용법은 빈 등록시
와 의존성 주입시
양쪽
모두에다가 @Qualifier
어노테이션을 사용해야합니다.
빈 등록
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
의존성 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Qualifier
주입 순서는 Qualifier 끼리 비교
-> Qualifier() 에 적힌 내용으로 빈 이름 검색
-> 못찾으면 예외발생
3. @Primary (주요한/우선순위)
주요한
이라는 단어 뜻 처럼 중요도를 매깁니다. 2개 의 Bean 검색되었을 시 @Primary
어노테이션을 사용하여 Bean으로 등록된 Bean을 선택하게 됩니다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
@Qualifier 와 @Primary 둘 중 누가 더 우선일까?
@Qualifier
가 더 우선, 왜? @Qualifier
는 메타 정보를 토대로 콕! 집어내는 성격이라 더 우선순위가 높음