생성자, 세터 등의 방법으로 의존성 주입하려 할 때, 필요한 의존 객체의 타입에 해당하는 빈을 찾아 주입해주는 어노테이션
Spring에서 등록된 Bean을 사용하기 위해 DI(의존성 주입)을 처리하는 방법은 크게 3가지가 있다.
Spring 3.x버젼까지만 해도 Setter Inject을 권장하였으나,
최근에는 순환참조, Coupling등이 문제로 인해서 Spring 4.3 이후 버젼 부터는 Contructor Inject를 권장하고 있다.
현재 가장 권장하고 있는 방법. 하나의 생성자가 존재시 기존 Field 주입의 단점을 극복해낸 패턴
만약 BookRepository가 빈으로 등록이 안되어있다면 직관적으로 빈이 등록이 되어있지 않은 것을 알 수 있고 에러가 난다.
null을 주지않는 한 nullPointerException이 발생하지 않는다.
의존관계 주입을 하지 않은 경우에는 BookService 인스턴스를 생성할 수 없다. 즉, 의존 관계에 대한 내용을 외부로 노출시킴으로써 컴파일 타임에 오류를 잡아낼 수 있다.
final로 선언이 가능하다. final은 누군가가 BookService 내부에서 BookRepository 객체를 바꿔칠 수 없게한다. (final로 선언된 변수는 반드시 선언과 함께 초기화되어야하므로 생성자 주입만 가능)
순환 참조인 경우 StackOverflowError을 발생시킴으로써 순환 의존성을 알 수 있다. 필드 주입과 setter 주입은 객체 생성시점에는 순환참조가 일어나는지 아닌지 발견할 수 있는 방법이 없다.(필드 주입과 setter 주입은 먼저 빈을 생성한후, 주입하려는 빈을 찾아 주입한다.) 생성자 주입에서는 컨테이너가 빈을 생성하는 시점에서 객체 생성에 사이클 관계가 생기기 때문이다.(생성자 주입은 먼저 생성자의 인자에 사용되는 빈을 찾거나 빈 팩토리를 만듭니다. 그 후에 찾은 인자 빈으로 주입하려는 빈의 생성자를 호출합니다. 즉, 먼저 빈을 생성하지 않고 주입하려는 빈을 먼저 찾습니다.)
단위 테스트 작성하기 좋다: 필드 주입과 setter 주입에서는 빈이 주입되어있으면 mockito로 목킹하여 가짜 객체 생성 후 테스트를 해야한다. 생성자 주입의 경우는 단순히 원하는 객체를 생성한 후, 생성자에 넣어주면 된다.
순환 참조 : 서로서로 주거니 받거니 함수를 반복하면서 끊엄없이 호출. A->B 참조하면서 B->A 참조하는 경우
@Service
@AllArgsConstructor
public class BookService {
private BookRepository bookRepository;
}
immutability 이슈까지 해결하고 싶다면 (final 사용)
@Service
@RequiredArgsConstructor
public class BookService {
private final BookRepository bookRepository;
}
만약 BookRepository가 빈으로 등록이 안되어있다면 BookService 인스턴스는 만들 수 있지만 BookRepository는 의존성 주입에 실패한다.
@Autowired(required=false)
...
그러므로 위와같이 required = false 옵션을 두어 BookRepository 빈이 없어도 된다라고 명시해줘야 한다.
class BookService {
@Autowired(required=false)
private BookRepository bookRepository;
public void setBookRepository(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
public void callRepository() {
bookRepository.doSomthing();
}
}
위 코드에서는 BookService는 BookRepository를 주입하지 않아도 인스턴스 생성이 가능하다. 그런데 callRpository()메서드에서 bookRepository.doSomthing()을 호출하고 있으므로
위와 같은 문제가 있으므로 이를 해결한 방법이 생성자 주입이다.
가장 간단한 방법으로 Bean으로 등록된 객체를 사용하고자 하는 클래스에 Field로 선언한 뒤 @Autowired키워드를 붙여주면 자동으로 주입된다.
만약 BookRepository가 빈으로 등록이 안되어있는 상황이 있을 수 있기 때문에 required=false를 둠.
매우 간단한 방법이지만, 단점이 많아서 권장되고 있지 않은 방법
동일한 Class의 Bean이 여러개 존재하는 경우 BookService는 의존성 주입에 실패한다.
그러므로 위와 같이 @Qualifier("...")를 붙여주어 bean 이름을 지정하여 주입 받는 것이 가능하다.
같은 이름을 못찾으면 에러가 빈 주입에 실패한다.
BookRepository 인터페이스를 상속받는 MyBookReposiroty, YourBookRepository가 있을 때 @Qualifier를 쓰면 MyBookRepository와 YourBookRepository의 스몰케이스로 빈 이름을 찾는다.
@Autowired @Qualifier("myBookRepository")// 스몰케이스인 빈 이름에 맞는 클래스를 찾음
BookRepository 인터페이스를 상속받는 MyBookReposiroty, YourBookRepository가 있을 때
주로 사용할 빈을 @Primary 어노테이션을 붙여 주입가능하다.
필드이름 myBookRepository와 동일한 MyBookRepository를 주입받아준다.
어떤 빈이 BookService에 주입받는지 확인하고 싶을 때 ApplicationRuunner를 사용한다.
printBookRepository()를 통해 어떤 빈이 주입되었는지 출력되었다.
BeanPostProcessor는 빈의 initializing(초기화) 라이프 사이클 이전, 이후에 필요한 부가 작업을 할 수 있는 라이프 사이클 콜백이다.
BeanPostProcessor : 새로 만든 빈 인스턴스를 수정 할 수 있는 라이프 사이클 인터페이스
그리고 BeanPostProcessor의 구현체인 AutowiredAnnotationBeanPostProcessor가 빈의 초기화 라이프 사이클 이전, 즉 빈이 생성되기 전에 @Autowired가 붙어있으면 해당하는 빈을 찾아서 주입해주는 작업을 하는 것이다.
AutowiredAnnotationBeanPostProcessor extends BeanPostProcessor : 스프링이 제공하는 @Autowired와 @Value 애노테이션 그리고 JSR-330의 @Inject 애노테이션을 지원하는 애노테이션 처리기.
AutowiredAnnotationBeanPostProcessor는 하나의 빈으로써 spring IoC 컨테이너에 등록되어 있다.
BeanFactory(ApplicationContext)는 BeanPostProcessor 타입의 빈 = AutowiredAnnotationBeanPostProcessor 빈을 꺼내 일반적인 빈들 = @Autowired로 의존성 주입이 필요한 빈들에게 @Autowired를 처리하는 로직을 적용한다.
ex) @PostConstruct : 해당 빈이 만들어진 후 해야할 일 정의할 수 있다.
참고
https://leejisoo860911.tistory.com/2
https://jackjeong.tistory.com/41
https://yaboong.github.io/spring/2019/08/29/why-field-injection-is-bad/