일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Spring
- nestJS
- OpenCV
- spring boot
- @Component
- nestjs auth
- 코테
- AWS
- 컴포넌트스캔
- 파이썬
- 가상면접사례로배우는대규모시스템설계기초
- C++
- C언어
- 카카오 코테
- python
- 구조체배열
- git
- nestjs typeorm
- 시스템호출
- 알고리즘
- 스프링
- 카카오 알고리즘
- @Autowired
- 코딩테스트
- 해시
- 프로그래머스
- thymeleaf
- TypeORM
- Nodejs
- 카카오
- Today
- Total
공부 기록장 💻
[Spring] 의존 관계 자동 주입의 4가지 방법과 특징, 생성자를 통해 주입해야하는 이유 본문
[Spring] 의존 관계 자동 주입의 4가지 방법과 특징, 생성자를 통해 주입해야하는 이유
dream_for 2023. 2. 3. 13:27
스프링의 컴포넌트 스캔 기능을 이용하여, 자동으로 스프링 빈을 컨테이너에 등록하고, 생성자에 적용된 @Autowired를 통해 해당 빈의 의존 관계를 자동으로 주입(DI) 한다는 것에 대해 학습하였다.
이번에는 @Autowired를 활용해 의존 관계를 주입하는 방법 4가지와 각각의 특징에 대해 배워보자.
그리고, 왜 생성자를 통한 의존 관계 주입을 사용해야 하는지 이유를 알아보자.
의존 관계 주입 방법 4가지
스프링 컨테이너가 관리하는 스프링 빈에 대하여, 의존 관계를 주입하는 방법에는 다음과 같이 크게 4가지의 방법이 있다.
- 생성자 주입
- 수정자 주입 (setter 주입)
- 필드 주입
- 일반 메서드 주입
단, 의존 관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작한다. 스프링 빈이 아닌 클래스에서 @Autowired 코드를 적용해도 아무 기능도 동작하지 않는다.
1. 생성자 주입
생성자를 통해 의존 관계를 주입 받는 방법이다.
컴포넌트 스캔을 하며 스프링 빈을 등록할 때 생성자를 실행하게 되는데 이 때, @Autowired 가 적용된 경우 스프링 컨테이너에서 의존 관계에 있는 스프링 빈을 꺼내서 주입해주는 방법을 말한다.
특징
- 생성자 호출 시점에 딱 1번만 호출되는 것이 보장된다.
- 불변, 필수 의존 관계에 사용한다.
- (불변 - 변경을 허용해서는 안 되는 경우. 제약을 더하고 싶은 경우)
- (필수 - 생성자로 들어오는 값이 필수적으로 존재해야 하는 제약을 두어야 하는 경우)
- 생성자가 1개만 있는 경우 @Autowired 생략이 가능하다.
다음과 같이 OrderServiceImpl의 경우 생성자에 @Autowired가 적용되어 있다. 따라서 스프링 컨테이너로부터 MemberRepository 구현체와 DiscountPolicy 구현체를 꺼내 의존 관계를 주입해준다.
생성자가 단 한 개만 존재하는 경우이므로, @Autowired 생략이 가능하다.
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
2. 수정자 주입 (Setter Injection)
수정자 주입 방법은 setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해 의존 관계를 주입하는 방법이다.
스프링 라이프 사이클에 따르면 먼저 1) 스프링 빈 등록 단계 가 진행되고, 이후에 2) 스프링 의존 관계 주입 단계가 진행되는데, 수정자에 의한 주입은 스프링 빈 등록이 먼저 진행 된 후에 발생한다고 볼 수 있다.
특징
- 선택, 변경 가능성이 있는 의존 관계에 사용
- (선택 -
@Autowired(required = false)
로 지정하면, 스프링 빈으로 등록되어 있지 않은 의존 관계에 대해서도 사용이 가능하다. @Autowired는 기본적으로 주입할 대상이 없을 시 오류가 발생함)
- (선택 -
- 자바 빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법이다.
- 필드의 값을 직접 변경하지 않고, setXxx, getXxx 라는 메서드를 통해 값을 읽거나 수정하는 규칙을 말한다.
아래의 경우, OrderServiceImpl 객체를 포함한 모든 객체들이 스프링 빈으로 등록이 모두 완료된 후(생성자),
이후 @Autowired 가 적용된 setter들을 통해 의존 관계가 주입이 된다.
@Component
public class OrderServiceImpl implements OrderService{
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
3. 필드 주입 (Field Injection)
아래와 같이 필드에 바로 주입하는 방법이다. 간결한 코드로 필드에 의존 관계를 주입할 수 있지만, 사용하지 않는 것이 권장된다. 애플리케이션의 실제 코드와 상관 없는 테스트 코드 (@SpringBootTest) 또는 Configuration 같은 곳에서만 특수한 목적으로만 사용한다.
@Component
public class OrderServiceImpl implements OrderService{
@Autowired private MemberRepository memberRepository;
@Autowired private DiscountPolicy discountPolicy;
}
4. 일반 메서드 주입
수정자 주입과 비슷하게, 일반 메서드로 의존 관계를 주입 받을 수 있다.
특징
- 한 번에 여러 필드를 주입받을 수 있다.
- 일반적으로 잘 사용하지 않는다.
@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;
}
}
@Autowired를 통한 자동 의존 관계 주입 방법 4가지에 대해 알아보았지만, 앞으로 생성자 주입을 사용해 DI를 수행하도록 하자.
생성자 주입을 사용하도록 하자.
최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장하고 있다.
생성자 주입 방식을 선택하는 가장 큰 이유는 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법이기 때문이다.
그 이유를 살펴보도록 하자.
1. 의존 관계의 불변을 보장한다.
생성자 주입은 객체를 생성할 때 딱 1번만 호출 되므로 이후에 호출 되는 일이 없기 때문이다.
대부분의 의존 관계는 한번 일어나면 애플리케이션 종료시점까지 의존 관계를 변경할 일이 없다. (그리고 사실 변해서도 안 된다.)
따라서 수정자 주입(setXxx 메서드)을 통해 변경하면 안되는 메서드를 public으로 열어두는 것은 좋은 설계 방법이 아니다.
항상 생성자 주입을 선택하되, 가끔 옵션이 필요한 경우에(필수 값이 아닌 경우에는) 수정자 주입 방식으로 옵션을 부여하면 된다.
2. 테스트를 통해 의존 관계 누락을 사전에 확인 가능하다.
다음과 같이 스프링 빈으로 등록되는 OrderServiceImpl 구현체가 있다고 해보자.
구현체는 생성자를 통해 MemberRepository, DiscountPolicy 의존 관계를 필수적으로 요구하고 있다.
예를 들어, 다음과 같이 OrderServiceImpl이 생성자가 아닌 setter 수정자를 통한 의존 관계를 주입하고 있다고 하면,
@Component
public class OrderServiceImpl implements OrderService{
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy){
this.discountPolicy = discountPolicy;
}
}
다음과 같이 테스트 코드를 실행하게 되면 NullPointException 에러가 발생한다.
필요한 MemberRepository, DiscountPolicy 객체가 생성되지 않았으며, 의존 관계가 누락되었기 때문에 이는 당연한 결과다.
그럼 만약 다음과 같이 생성자를 통한 의존 관계를 주입했다면?
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
컴파일 에러가 발생하여, 필수적으로 요구되는 의존 관계가 누락됐음을 단번에 알아차릴 수 있게 된다.
IDE에서도 빨간 밑줄이 나타나, 바로 어떤 값을 필수로 주입해야 하는지 알 수 있다.
다음과 같이 순수한 자바 코드를 통해 단위 테스트를 수행할 때, 필요한 Mock 구현체들을 직접 임시로 생성하여 테스트를 수행하면 된다.
(추후 Mock 라이브러리를 이용해보고자 한다.)
3. final 키워드를 사용할 수 있다.
생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다. 따라서 생성자에 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.
(컴파일 오류는 가장 빠르고, 좋은 오류이다!)
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, final 키워드를 사용할 수 없다. 오직 생성자 주입 방식만 final 키워드 사용을 허용한다. (즉 final 키워드를 사용하면, 생성자를 통한 초기화가 필수적이다.)