본문 바로가기
Back-end/Spring 개념

12. Dependency Injection 방법

by devraphy 2022. 2. 11.

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만을 이용하여 구현할 수 있다. 

댓글