본 글은 인프런 김영한님의 스프링 완전 정복 로드맵을 기반으로 정리했습니다.
컴포넌트 스캔을 통해 빈을 등록하면 설정파일에서 의존관계를 주입할 수 없기 때문에 의존 관계를 주입해줄 다른 방법이 필요하다. 이 때 사용되는 것이 의존관계 자동 주입 @Autowired
이다.
@Autowired
의존 관계 자동 주입은 꼭 컴포넌트 스캔과 함께 사용해야만 하는 것은 아니다. 수동으로 빈을 등록할 때도 의존관계 자동 주입을 사용할 수 있다.
과거에는 의존관계 자동 주입에대한 호불호가 있었지만, 최근에는 스프링 부트와 함께 컴포넌트 스캔과 의존관계 자동 주입을 적극 사용하는 추세다.
의존관계 자동 주입을 사용하는 방법은 간단하다. 의존을 주입할 대상에 @Autowired
애노테이션을 붙이면 된다. @Autowired
를 통해 의존관계 자동 주입을 적용할 수 있는 방법은 4개다.
@Service
public class OrderService {
private final MemberRepository memberRepository;
@Autowired
public OrderService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
생성자는 객체 생성 시점에 단 한번만 호출된다. 자바에서 생성자에서 초기화 하는 필드에는 final
키워드를 붙일 수 있다. 아래에서 다룰 다른 방법들은 생성자 호출 이후에 실행되므로 final
키워드를 붙일 수 없다.
final
키워드를 붙임으로써 얻을 수 있는 이점이 두 가지 있다.
final
키워드를 붙이고 초기화해주지 않으면 컴파일 에러가 난다. 컴파일 단계에서 의존객체를 초기화하지 않는 실수를 감지해서 런타임에서 NPE가 터지는 일을 방지할 수 있다. 즉, 필수 의존 관계임이 보장된다.
final
변수는 한 번만 초기화 할 수 있다. 즉, 불변성이 보장된다.
대부분의 의존 객체는 필수값이고 바꿀 일이 없기 때문에 가장 많이 쓰이는 방법이다.
스프링 4.3버전부터 생성자가 1개면 @Autowired
키워드를 생략해도 자동으로 의존관계를 주입해준다. 생성자가 2개 이상이면 생략하면 안 된다. 컨테이너가 어떤 생성자를 통해 의존 관계를 주입해야할 지 모르기 때문이다.
롬복 라이브러리를 쓰면 코드를 더 줄일 수 있다. @RequiredArgsConstructor
가 final
이붙은 필드를 매개변수로 받는 생성자를 자동으로 만들어준다. 아래 코드는 위와 동일하다.
@Service
@RequiredArgsConstructor
public class OrderService {
private final MemberRepository memberRepository;
}
@Service
public class OrderService {
private MemberRepository memberRepository;
@Autowired
public setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
세터를 통해 의존관계를 주입받는 방법이다. 빈이 컨테이너에 등록될 때 세터가 호출되서 의존관계가 주입된다. 세터를 정의 했기 때문에 의존 객체를 변경할 수 있다. 의존 객체를 변경할 일이 있다면 세터를 통해 주입받도록 하자.
@Autowired
를 사용하면 주입할 타입의 빈이 컨테이너에 없다면 오류가 난다. 주입할 대상이 필수 의존관계가 아니라면 @Autowired(required=false)
로 지정하면 된다. 즉, 선택적 의존관계에 사용할 수 있다. 선택적 의존관계를 처리하는 방법은 두 가지가 더 있는데 이는 밑에서 다루도록 하겠다.
주의할 점이 있다. 컴포넌트 스캔을 사용하지 않고 수동으로 빈을 등록하면서 세터를 사용할 수 있다. 이 때, 세터로 주입한 객체와 @Autowired
가 주입해준 객체가 다르면 세터를 통해 수동으로 주입한 객체가 무시된다. 이는 찾기 어려운 버그가 될 가능성이 있다. 의존관계 자동 주입을 사용했다면 자동 주입이 어려운 경우를 제외하고 일관되게 자동 주입을 사용하도록 하자.
@Service
public class OrderService {
@Autowired
private MemberRepository memberRepository;
}
필드에 바로 주입받는 방식이다. 코드는 간단하지만 의존 객체를 변경할 수 없다는 치명적인 단점이 있다. OrderService를 유닛 테스트 하기위해 MemberRepository를 모의 객체로 주입하고 싶을 때 순수한 자바 코드로 MemberRepository를 설정할 방법이 없다. 테스트하기가 어려워진다는 단점은 치명적이다.
필드 주입은 @SpringBootTest
로 스프링 컨테이너를 띄워서 테스트하는 테스트 코드나 @Configuration
이 붙은 스프링 설정 클래스와 같은 제한적인 경우에만 사용하자.
@Service
public class OrderService {
private MemberRepository memberRepository;
private OrderRepostiroy orderRepostiroy;
@Autowired
public void init(MemberRepository memberRepository,
OrderRepository orderRepository) {
this.memberRepository = memberRepository;
this.orderRepostiroy = orderRepostiroy;
}
}
일반 메서드를 통해 한 번에 여러 객체를 주입 받을 수 있다. 거의 사용되지 않는다.
자동 주입할 빈이 없어도 오류를 내지 않고 동작하게 하고 싶을 수 있다. 이를 처리하는 방법은 3개다.
Autowired(required=false)
@Service
public class OrderService {
private MemberRepository memberRepository;
@Autowired(required=false)
public setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
@Autowired
에 아무런 옵션도 주지 않았을 때 자동 주입할 객체가 컨테이너에 없으면 오류가 발생한다. required의 기본 옵션이 true이기 때문인데 이 옵션을 false로 설정하면 객체가 컨테이너에 없을 때 객체를 호출하지 않기 때문에 오류가 발생하지 않는다.
세터에서 사용할 수 있다.
org.springframework.lang.@Nullabe
@Service
public class OrderService {
private MemberRepository memberRepository;
@Autowired
public setMemberRepository(@Nullable MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
자동 주입할 객체가 없으면 null
이 입력된다.
생성자에서도 사용할 수 있다.
Optional<>
@Service
public class OrderService {
private MemberRepository memberRepository;
@Autowired
public setMemberRepository(Optional<MemberRepository> memberRepository) {
if (memberRepository.isPresent()) {
this.memberRepository = memberRepository.get();
} else {
this.memberRepository = null;
}
}
}
자동 주입할 객체가 존재하지 않으면 Optional.empty
가 입력된다. Optional
은 자바8에 추가된 문법으로 null 처리를 편리하게 만들어준다.
생성자에서도 사용할 수 있다.
@Autowired 는 타입을 기반으로 빈을 조회해서 주입해준다. 그런데 조회된 빈이 2개 이상이면 컨테이너는 어떤 빈을 주입해야할지 모르기 때문에 NoUniqueBeanDefinition
오류를 발생시킨다.
@Component
public class MemoryMemberRepository implements MemberRepository {}
@Component
public class JpaMemberRepository implements MemberRepository {}
@Service
public class OrderService {
private MemberRepository memberRepository;
@Autowired
public OrderService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
MemberRepository 타입의 빈이 2개다. 주입받을 객체의 타입을 구체 타입으로 바꿔주면 충돌이 안나지만 객체를 구체 타입으로 다루면 유연성이 떨어진다.
추상 타입으로 주입받으면서 충돌을 해결하는 방법은 다음과 같다.
@Service
public class OrderService {
@Autowired
private MemberRepository memoryMemberRepository;
}
@Service
public class OrderService {
private MemberRepository memberRepository;
@Autowired
public OrderService(MemberRepository memoryMemberRepository) {
this.memberRepository = memoryMemberRepository;
}
}
주입받는 필드명, 혹은 파라미터 명을 주입받는 빈의 이름으로 변경하면 충돌이 나지 않는다. @Autowired
는 타입으로 매칭해서 빈이 2개이상 조회되면 빈의 이름으로 추가 매칭한다.
@Qualifier
@Qualifier
는 자동주입 대상인 빈을 구분하는 추가적인 방법이다. 빈의 이름을 변경하는 것이 아니라는 것에 주의하자.
@Component
@Qualifier("memoryMemberRepository")
public class MemoryMemberRepository implements MemberRepository {}
@Component
@Qualifier("mainMemberRepository")
public class JpaMemberRepository implements MemberRepository {}
@Service
public class OrderService {
private MemberRepository memberRepository;
@Autowired
public OrderService(
@Qualifier("mainMemberRepository") MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
@Qualifer
애노테이션을 붙여주면 해당 애노테이션이 붙은 빈을 주입받는다. 만약 매칭되는 빈이 없으면 @Qualifier
의 이름을 가진 빈을 추가적으로 찾는다. 그래도 없으면 NoSuchBeanDefinition
예외가 발생한다.
@Qualifier
로 빈의 이름을 찾으려 하지말고 매칭되는 @Qualifier
를 찾는 용도로만 즉, 쌍으로 사용하는게 의도가 명확하고 유지보수 하기에 좋다. @Qualifier
는 빈을 구분하는 방법이므로 @Autowired
가 붙은 곳에 모두 적용할 수 있다.
@Primary
@Component
public class MemoryMemberRepository implements MemberRepository {}
@Component
@Primary
public class JpaMemberRepository implements MemberRepository {}
@Service
public class OrderService {
private MemberRepository memberRepository;
@Autowired
public OrderService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
여러개의 빈이 조회될 경우 예외를 발생시키기보다 @Primary
를 통해 우선권을 가지는 빈을 지정해줄 수 있다.
팁으로 주로 사용하는 빈을 @Primary
로 등록하고 덜 사용되는 빈만 @Qualifier
로 명시적으로 사용하면 유지보수 하기에 좋다.
컴포넌트 스캔과 의존관계 자동 주입은 매우 편리하다.@Component
애노테이션과 @Autowired
두 애노테이션 만으로 @Configuration
이 붙은 설정 클래스에 @Bean
애노테이션이 붙은 메서드를 정의하고 의존관계를 생성자 혹은 수정자로 일일이 넣어줘야 하는 수고를 줄일 수 있다.
하지만 수동으로 빈을 등록하는 것이 더 좋을때가 있다. 바로 애플리케이션에 광범위하게 영향을 미치는 AOP 처리다. 데이터베이스 연결, 공통 로그 처리와 같은 애플리케이션 전반에 걸친 관심사는 보통 AOP로 처리한다. 이런 큰 영향을 끼치는 빈은 자동으로 등록했다가 오작동하면 그 이유를 알기가 쉽지 않다. 자동 등록은 @Controller
, @Service
, @Repository
처럼 정형화된 패턴이 있는 빈에 사용하고 AOP처럼 애플리케이션 전반에 영향을 끼치는 빈은 수동으로 등록해서 명시적으로 들어내는 것이 유지보수 하기에 좋다.