0. 개요
- 이전 포스팅에서 의존성 자동 주입 방법과 그 원리에 대해서 배웠다.
- 이번 포스팅에서는 Spring에서 제공하는 다양한 DI 자동 주입 방법에 대해서 알아보자.
1. Constructor(생성자) Injection
- Spring은 생성자 주입 방식의 사용을 권장한다.
a) 구현 방법
- @Component가 붙은 클래스 내부에 생성자를 만든다.
- 생성자에 @Autowired를 부착한다.
@Component
public class TestServiceImpl implements TestService {
private final TestRepository testRepository;
@Autowired
public TestServiceImpl(TestRepository testRepository) {
this.testRepository = testRepository;
}
}
b) Constructor Injection의 적용 조건
- 생성자는 최초 빌드 과정에서 딱 한 번만 실행된다.
- 그러므로 값이 변하지 않으며, 의존관계가 변경될 수도 없다.
- 이처럼 생성자 주입은 불변/필수 의존관계를 형성할 때 사용된다.
- 여기서 말하는 불변/필수 의존관계란, 프로그램이 종료될 때까지 변하지 않는 의존관계를 의미한다.
c) 멤버 변수에 final이 붙는 이유
- 프로그램은 종료되기 전까지 그 흐름이 변하지 않는다. 즉, 의존관계가 유지된다.
- 생성자 주입 방식의 불변성을 유지하기 위해서 final 키워드를 사용한다.
- 최초의 생성자 호출에 의해 생성된 객체의 값을 변함없이 유지하기 위해서다.
d) Constructor Injection의 동작원리
- Spring은 Bean 등록을 끝내고 의존관계를 형성하는데, 생성자를 이용한 DI 주입은 이 흐름을 위배한다.
- 생성자 주입을 사용하면 Bean 등록과 동시에 의존관계를 형성하기 때문이다.
- Bean을 등록하기 위해서는 객체를 생성해야 하고, 객체를 생성하기 위해서는 생성자를 호출해야 한다.
- 생성자를 호출하면 내부에 존재하는 의존관계를 형성해야만 해당 객체를 생성 및 사용할 수 있다.
- 그러므로 생성자를 사용한 DI 방식은 Bean 등록과 동시에 의존관계를 형성한다.
- 생성자 주입 방식은 의존관계를 형성하는 대상이 모두 Bean으로 등록되어 있지 않으면 오류를 발생한다.
- 이 부분이 위에서 언급한 필수 의존관계를 의미한다. 의존관계를 형성하는 Bean은 반드시 존재해야 한다는 것이다.
e) @Autowired를 생략할 수 있다.
- @Component가 붙은 클래스 내부에 생성자가 딱 한 개만 존재하는 경우, @Autowired를 생략할 수 있다.
- 이는 Spring Boot에서 제공하는 기능이다.
2. Setter(수정자) Injection
a) 구현 방법
- Setter에 @Autowired를 부착한다.
@Component
public class TestServiceImpl implements TestService {
private TestRepository testRepository;
@Autowired
public void setTestRepository(TestRepository testRepository) {
this.testRepository = testRepository;
}
}
b) Setter Injection의 적용 조건
- Setter 주입은 선택적인 또는 변경 가능한 의존관계를 구현하는 데 사용된다.
- Setter 주입은 멤버 변수(필드)의 값을 변경하는 Setter를 통해 의존관계를 주입받는 방식이다.
- Spring으로부터 주입받는 Bean을 초기화하거나, Bean의 값이 변경될 가능성이 있을 때 사용한다.
c) Setter Injection을 사용하지 않는 이유 (1)
- Setter는 public이므로 외부에서 임의로 객체의 값을 변경할 수 있다는 것이 위험요소가 된다.
- 이처럼 외부에서 프로그램의 흐름을 임의로 변경할 수 있는 구조는 좋은 구조가 아니다.
d) Setter Injection을 사용하지 않는 이유(2)
- 위의 예시코드에서 testRepository의 값이 Null이더라도 TestServiceImpl 클래스를 Bean으로 등록하는데 문제가 없다.
- 문제가 되는 시점은 TestServiceImpl이 testRepository의 메소드를 호출하는 시점이다.
- testRepository의 구현체가 생성되어 있지 않은 경우, NullPointerException이 발생하기 때문이다.
- 이처럼 의존관계 대상이 되는 객체가 Null 값이라도 문제가 되지 않는 이유는 필수 값의 개념이 존재하지 않기 때문이다.
3. Field(필드, 멤버 변수) Injection
a) 구현 방법
- 멤버 변수에 @Autowired를 부착한다.
@Component
public class TestServiceImpl implements TestService {
@Autowired private TestRepository testRepository;
}
b) Field Injection을 사용하지 않는 이유
- 멤버 변수를 직접 의존관계의 대상으로 지정하므로, 구현체(Bean)의 값에 접근하거나 변경할 수 있는 방법이 없다.
- 오직 Spring Container에서 해당 멤버 변수의 타입에 해당하는 Bean을 주입하는 방법뿐이다.
- 그러므로 이를 원활하게 사용하기 위해서는 Getter와 Setter를 필요로 하는데, 이는 Setter Injection과 차이가 없다.
- Field Injection은 테스트 환경에서 사용이 불편하다.
- 구현체를 다른 것으로 변경할 수 있는 방법이 없으므로, 반드시 Container를 거치는 테스트를 해야 한다.
- 즉, 순수 Java로 테스트할 수 없으므로 의존성 주입이 Spring Framework에 종속적이게 된다.
- Spring은 Field Injection은 권장하지 않으며, 사용하더라도 Spring이 자체적으로 경고를 띄운다.
4. 결론 - 생성자 주입 방식을 사용하자.
- 생성자를 이용한 DI 방식을 사용하자.
- 이는 Spring에서 권장하는 방법이기도 하지만, 좋은 객체지향 프로그램의 구조를 구현하는 방법이기도 하기 때문이다.
- 여기에는 다음과 같은 이유가 존재한다.
a) 불변성 보장
- Spring은 build될 때 딱 한번만 의존관계를 형성한다.
- 그러므로 Application이 종료될 때까지 의존관계를 변경하지 않는 것이 좋은 설계구조다.
- 생성자는 프로젝트가 빌드되는 최초에 호출된 이후 호출되지 않는다.
- 그러므로 불변적 프로그램 흐름을 위한 설계에 최적화 되어있는 DI 방식이다.
b) 누락이 없다. (필수 값의 개념)
- Setter 주입의 경우, 멤버변수의 구현체가 존재하지 않더라도 해당 클래스의 객체를 생성하는데 문제가 없다.
- 하지만 생성자 주입의 경우, 의존성 관계의 대상이 되는 모든 구현체가 존재해야만 객체를 생성할 수 있다.
- 그러므로 Setter 주입처럼, 추 후에 객체의 값이 Null이라서 발생하는 문제를 예방할 수 있다.
c) final 키워드의 사용
- final 키워드를 사용으로 불변성과 누락 방지를 동시에 보장한다.
- final 키워드는 한번 설정된 값을 변경할 수 없게 한다. → 불변성 보장
- final 키워드를 사용하면 해당 멤버 변수의 초기 값 설정이 누락된 경우, 이를 오류로 알려준다. → 누락 방지
d) Framework에 종속적이지 않다.
- 생성자 주입은 Spring Framework에 종속적이지 않고, 순수한 Java의 특성을 살리는 방법이다.
- 그러므로 Test 코드를 작성할 때에도, Spring Container 없이 순수한 Java만을 이용하여 구현할 수 있다.
'Back-end > Spring 개념' 카테고리의 다른 글
14. Annotation을 만드는 방법 (0) | 2022.02.15 |
---|---|
13. @Autowired의 다양한 문제 해결방법 (0) | 2022.02.14 |
11. Dependency Injection 기본개념 (0) | 2022.02.10 |
10. Bean 자동 vs 수동 등록 (0) | 2022.02.09 |
9. @ComponentScan의 동작원리와 옵션 (0) | 2022.02.08 |
댓글