티스토리 뷰
의존성 주입과 빈 등록은 다른 것이다. IoC 컨테이너에 빈으로 등록이 되어야 의존성 주입을 할 수 있다. setter와 생성자를 통해서 IoC/DI로 의존성을 주입하였는데 이번에는 @Autowired라는 어노테이션을 이용해서 의존성을 주입하는 것에 대해 알아보려 한다.
# @Autowired란?
필요한 의존 객체의 “타입"에 해당하는 빈을 찾아 의존성을 주입한다.
- 생성자
- setter
- 필드
[ 1. 생성자 주입(Constructor Injection) ]
@Service
public class UserServiceImpl implements UserService {
private UserRepository userRepository;
private MemberService memberService;
@Autowired
public UserServiceImpl(UserRepository userRepository, MemberService memberService) {
this.userRepository = userRepository;
this.memberService = memberService;
}
}
생성자 주입은 생성자의 호출 시점에 1회 호출 되는 것이 보장된다. 그렇기 때문에 주입받은 객체가 변하지 않거나, 반드시 객체의 주입이 필요한 경우에 강제하기 위해 사용할 수 있다. 또한 Spring 프레임워크에서는 생성자 주입을 적극 지원하고 있기 때문에, 생성자가 1개만 있을 경우에 @Autowired를 생략해도 주입이 가능하도록 편의성을 제공하고 있다. 그렇기 때문에 위의 코드는 아래와 동일한 코드가 된다.
@Service
public class UserServiceImpl implements UserService {
private UserRepository userRepository;
private MemberService memberService;
public UserServiceImpl(UserRepository userRepository, MemberService memberService) {
this.userRepository = userRepository;
this.memberService = memberService;
}
}
생성자 주입은 생성자에 의존성 주입을 받고자 하는 field를 나열하는 방법으로, 권고되는 방법의 하나 이다.
- 장점
- 필수적으로 사용해야 하는 레퍼런스 없이는 인스턴스를 만들지 못하도록 강제함
- Spring 4.3 이상부터는 생성자가 하나인 경우 @Autowired를 사용하지 않아도 됨
- Circular Dependency / 순환 참조2 의존성을 알아 차릴 수 있음
- 생성자에 점차 많은 의존성이 추가 될 경우 리팩토링 시점을 감지 할 수 있음
- 의존성 주입 대상 필드를 final로 불편 객체 선언할 수 있음
- 테스트 코드 작성시 생성자를 통해 의존성 주입이 용이함
- 단점
- 어쩔 수 없는 순환 참조는 생성자 주입으로 해결하기 어려움
- 이러한 경우에는 나머지 주입 방법 중에 하나를 사용
- 가급적이면 순환 참조가 발생하지 않도록 하는 것이 더 중요
- 어쩔 수 없는 순환 참조는 생성자 주입으로 해결하기 어려움
[ 2. 수정자 주입(Setter 주입, Setter Injection) ]
필드 값을 변경하는 Setter를 통해서 의존 관계를 주입하는 방법이다. Setter 주입은 생성자 주입과 다르게 주입받는 객체가 변경될 가능성이 있는 경우에 사용한다. (실제로 변경이 필요한 경우는 극히 드물다.)
@Service
public class UserServiceImpl implements UserService {
private UserRepository userRepository;
private MemberService memberService;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired
public void setMemberService(MemberService memberService) {
this.memberService = memberService;
}
}
@Autowired로 주입할 대상이 없는 경우에는 오류가 발생한다. 위의 예제에서는 XXX 빈이 존재하지 않을 경우에 오류가 발생하는 것이다. 주입할 대상이 없어도 동작하도록 하려면 @Autowired(required = false)를 통해 설정할 수 있다.
- 장점
- 의존성이 선택적으로 필요한 경우에 사용
- 생성자에 모든 의존성을 기술하면 과도하게 복잡해질 수 있는 것을 선택적으로 나눠 주입 할 수 있게 부담을 덜어줌
- 생성자 주입 방법과 Setter 주입 방법을 적절하게 상황에 맞게 분배하여 사용
- 단점
- 의존성 주입 대상 필드가 final 선언 불가
[ 3. 필드 주입(Field Injection) ]
필드에 바로 의존 관계를 주입하는 방법이다. IntelliJ에서 필드 인젝션을 사용하면 Field injection is not recommended이라는 경고 문구가 발생한다.
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private MemberService memberService;
}
필드 주입을 이용하면 코드가 간결해져서 과거에 상당히 많이 이용되었던 주입 방법이다. 하지만 필드 주입은 외부에서 변경이 불가능하다는 단점이 존재하는데, 점차 테스트 코드의 중요성이 부각됨에 따라 필드의 객체를 수정할 수 없는 필드 주입은 거의 사용되지 않게 되었다. 또한 필드 주입은 반드시 DI 프레임워크가 존재해야 하므로 반드시 사용을 지양해야 한다. 그렇기에 애플리케이션의 실제 코드와 무관한 테스트 코드나 설정을 위해 불가피한 경우에만 이용하도록 하자.
- 장점
- 가장 간단한 선언 방식
- 단점
- 의존 관계가 눈에 잘 보이지 않아 추상적이고, 이로 인해 의존성 관계가 과도하게 복잡해질 수 있음
- 반대로 Constructor injection과 Setter injection은 의존성을 명확하게 커뮤니케이션 함
- 이는 SRP / 단일 책임 원칙에 반하는 안티패턴
- DI Container와 강한 결합을 가져 외부 사용이 용이하지 않음
- 단위 테스트시 의존성 주입이 용이하지 않음
- 의존성 주입 대상 필드가 final 선언 불가
- 의존 관계가 눈에 잘 보이지 않아 추상적이고, 이로 인해 의존성 관계가 과도하게 복잡해질 수 있음
# @Autowired 경우의 수
Autowired를 사용할 때 경우의 수가 존재하는데 각각의 상황에 대해서 정리해보자.
1) 해당 타입의 빈이 없는 경우
2) 해당 타입의 빈이 한 개인 경우
3) 해당 타입의 빈이 여러 개인 경우
3-1) 빈 이름으로 시도
3-1-1) 같은 이름의 빈 찾으면 해당 빈 사용
3-1-2) 같은 이름 못 찾으면 실패
1) 해당 타입의 빈이 없는 경우
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookService {
BookRepository bookRepository;
@Autowired
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
}
생성자에 대해 Autowired 어노테이션이 있는 상태이다. BookRepository가 빈으로 등록되어 있지 않다고 가정하면 생성자에서 에러가 발생한다. 이유는 Autowired가 의존성을 주입하기 위해서는 빈이 등록되어 있는 객체중에서 찾는데 BookRepository라는 객체는 빈으로 등록되어 있지 않기 때문에 의존성 주입에 실패해서 에러가 발생한다. (Autowired의 기본값이 true이기 때문이다)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookService {
BookRepository bookRepository;
@Autowired
public void setBookRepository(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
}
현재 BookRepository는 빈으로 등록되어 있지 않다고 가정하고, Autowired가 붙은 setter에서 여전히 에러가 발생한다. 여기서 하나 궁금한 것은 이번에는 setter인데 BookService의 빈은 등록할 수 있지 않나? 맞다 BookSevice는 빈으로 등록할 수 있다. 그리고 Autowired가 기본값이 true라고 했는데 그러면 false로 하면 어떻게 될까?
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookService {
BookRepository bookRepository;
@Autowired(required = false)
public void setBookRepository(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
}
위와 같이 쓴다면 BookService의 객체는 빈으로 등록이 되지만 BookRepository는 빈으로 등록되지 않게 된다. (required = false이기 때문이다) 그리고 Autowired를 사용할 수 있는 곳이 필드가 하나 더 있다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookService {
@Autowired(required = false)
BookRepository bookRepository;
}
위와 같이 필드에도 사용할 수 있다. setter와 필드에 Autowired를 사용하게 되면 만약 BookRepository가 빈으로 등록되어 있지 않다 해도 BookService는 빈으로 등록이 가능하다. 하지만 생성자에 Autowired를 쓴 상황에서 BookRepository가 빈으로 등록되어 있지 않다면 BookService도 빈으로 등록되지 못하는 경우가 생긴다.
3) 해당타입의 빈이 여러 개인 경우
만약 BookRepository라는 인터페이스 아래에 MyBookRepository, YourBookRepository라는 클래스가 존재하고 둘다 빈으로 등록이 되어 있을 때 바로 위의 코드처럼 BookRepository 필드에 Autowired를 적용하면 어떻게 될까? 주입을 해줄 수 없게 된다. 왜냐하면 등록해야 할 빈이 2개이기 때문에 스프링은 어떤걸 해야할지 모르기 때문이다.
@Repository @Primary
public class MyBookRepository implements BookRepository {
// test
}
따라서 만약 같은 타입의 빈이 위의 상황처럼 여러개 일 때 내가 만약 MyBookRepository를 빈으로 등록하고 싶다면 @Primary 라는 어노테이션을 추가해주면 MyBookRepository가 빈으로 등록된다. 또 다른 방법으로는 @Qualifier라는 어노테이션을 이용하는 것인데 일반적으로 빈의 이름은 클래스 이름의 스몰케이스이다. 아래의 예시를 보자.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class BookService {
@Autowired @Qualifier("myBookRepository")
BookRepository bookRepository;
}
이런식으로 Autowired 옆에 @Qualifier라는 어노테이션을 이용하여 괄호안에는 등록하고자 할 빈의 이름을 적어주면 된다 하지만 @Qualifier보다 @Primary가 더 안전한 방법이기 때문에 @Primary를 더 추천한다. 그리고 마지막으로는 해당타입의 빈을 모두 주입받는 경우도 있다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookService {
@Autowired
List<BookRepository> bookRepositoryList;
}
위와 같이 List를 이용하면 BookRepository에 해당하는 타입의 빈을 모두 주입받을 수 있다. 여러 방법중에서 제일 추천하고 싶은 방법은 @Primary이다.
# @Autowired 동작원리
어떻게 @Autowired 어노테이션만으로 의존성 주입이 가능할까?
public interface BeanPostProcessor
BeanPostProcessor라는 라이프 사이클 인터페이스의 구현체인 AutowiredAnnotationBeanPostProcessor에 의해 의존성 주입이 이루어진다. BeanPostProcessor는 빈의 initializing(초기화) 라이프 사이클 이전, 이후에 필요한 부가 작업을 할 수 있는 라이프 사이클 콜백이다. 그리고 BeanPostProcessor의 구현체인 AutowiredAnnotationBeanPostProcessor가 빈의 초기화 라이프 사이클 이전, 즉 빈이 생성되기 전에 @Autowired가 붙어있으면 해당하는 빈을 찾아서 주입해주는 작업을 하는 것이다.
BeanPostProcessor
> 새로 만든 빈 인스턴스를 수정할 수 있는 라이프 사이클 인터페이스
AutowiredAnnotationBeanPostProcessor extends BeanPostProcessor
> 스프링이 제공하는 @Autowired와 @Value 애노테이션 그리고 JSR-330의 @Inject 애노테이션을 지원하는 애노테이션 처리기.
# 인프런 강의 (백기선 / 스프링 프레임워크 핵심 기술)
IoC 컨테이너 1부: 스프링 IoC 컨테이너와 빈 https://joinwithyou.tistory.com/24
IoC 컨테이너 2부: ApplicationContext와 다양한 빈 설정 방법 https://joinwithyou.tistory.com/25
IoC 컨테이너 3부: @Autowire https://joinwithyou.tistory.com/26
IoC 컨테이너 4부: @Component와 컴포넌트 스캔 https://joinwithyou.tistory.com/27
IoC 컨테이너 5부: 빈의 스코프 https://joinwithyou.tistory.com/28
IoC 컨테이너 6부: Environment 1부. 프로파일 https://joinwithyou.tistory.com/29
IoC 컨테이너 6부: Environment 2부. 프로퍼티 https://joinwithyou.tistory.com/30
IoC 컨테이너 7부: MessageSource https://joinwithyou.tistory.com/31
IoC 컨테이너 8부: ApplicationEventPublisher https://joinwithyou.tistory.com/32
IoC 컨테이너 9부: ResourceLoader https://joinwithyou.tistory.com/33
# Reference
https://devlog-wjdrbs96.tistory.com/165?category=882236 (블로그)
https://www.inflearn.com/course/spring-framework_core (백기선 강의)
'Spring' 카테고리의 다른 글
IoC 컨테이너 5부: 빈의 스코프 (0) | 2021.06.06 |
---|---|
IoC 컨테이너 4부: @Component와 컴포넌트 스캔 (0) | 2021.06.06 |
IoC 컨테이너 2부: ApplicationContext와 다양한 빈 설정 방법 (0) | 2021.06.06 |
IoC 컨테이너 1부: 스프링 IoC 컨테이너와 빈 (0) | 2021.06.06 |
Spring - DI (0) | 2021.06.06 |