티스토리 뷰

 Spring에서 의존성 주입(DI: Dependency Injection)을 하는 방법은 크게 3가지가 있다. 여기서 의존성 주입은 필요한 객체를 직접 생성하는 것이 아니라 외부에서 객체를 생성해서 넣어주는 방식이다. 

 

1. Field Injection

 Field injection은 의존성을 클래스 멤버 필드로 직접 주입하는 방식으로 @Autowired, @Inject 같은 주입 어노테이션을 사용한다. 비교적 코드가 간결하다는 장점을 가지고 있지만 외부에서 접근이 불가능하다는 등의 여러 문제점을 가지고 있어서 Spring 공식 문서에서도 Field injection 말고 Constructor injection 방식을 이용할 것을 권장하고 있다. 이에 대한 자세한 이유는 뒤에서 살펴보자.

@Controller
public class Controller{
  @Autowired 
  private Service service;
}

 

2. Setter Injection

 Setter injection은 setter 메서드를 통해 의존성 주입을 하는 방식이다. Setter injection은 Constructor injection과 다르게 주입 받는 객체가 선택과 변경의 가능성이 있는 경우 사용한다. 이때 @Autowired가 있는 수정자들을 자동으로 의존관계에 주입하기 때문에 주입할 대상이 없는 경우 오류가 발생한다. 주입할 대상이 없어도 동작하게 하기 위해서는 @Autowired(required=false)로 설정하면 된다. 

@Controller 
public class Controller{
   private Service service;

   @Autowired 
   public setService(Service service){
     this.service = service; 
   }
}

 

3. Constructor Injection

 Constructor injection은 생성자를 통해서 의존성 주입을 하는 방식이다. 이 방식은 호출 시점에 딱 1번만 호출되는 것이 보장된다. 이러한 특징은 주입 받은 객체가 변하지 않거나, 반드시 객체 주입이 필요한 경우 강제하기 위해서 사용할 수 있다. Spring 4.3 이후로는 클래스 생성자가 1개만 존재하고, 그 생성자가 주입 받을 객체가 빈으로 등록되어 있다면 @Autowired를 생략할 수 있다. 

@Controller 
public class Controller{
   private Service service;

   @Autowired 
   public Controller(Service service){
     this.service = service; 
   }
}

 

Constructor Injection 방식을 권장하는 이유

1. 객체 불변성 확보

 위에서 언급했듯이 Constructor injection 방식은 호출 시점에  최초 1회만 호출이 되기 때문에 의존관계의 변경이 일어나지 않아야 한다. 반면, Field injection 방식은 메서드를 public을 열어둠으로써 불필요하게 수정의 가능성을 열어두었기 때문에 유지보수성을 떨어뜨린다. 따라서 Constructor injection을 통해서 변경의 가능성을 배제하고 불변성을 보장하는 것이 좋다. 

 

2. 테스트 코드 작성 용이

 테스트가 특정 프레임워크에 의존하는 것은 침투적이므로 좋지 못하다. 그래서 가능하다면 순수 자바로 테스트를 작성하는 것이 좋다. 그러나 Constructor injection이 아닌 다른 주입으로 작성된 코드는 순수한 자바 코드로 단위 테스트를 작성하는 것이 어렵다. 메인 코드는 DI 프레임워크 위에서 동작하는데 단위테스트 시 단독적으로 실행되기 때문에 의존관계가 주입되지 않을 것이고, NullPointerException이 발생하게 된다. 따라서, 컴파일 시점에 객체를 주입 받아 테스트 코드를 작성할 수 있는 Constructor injection 방식을 추천한다. 

 

3. 순환참조 에러 방지

 순환 참조는 개발을 하다 보면 흔히 생길 수 있는 문제로 여러 컴포넌트 간에 의존성이 생기는 문제이다. 예를 들어서 A가 B를 참조하고, B가 다시 A를 참조하는 코드의 경우 순환참조라고 할 수 있다. 이때 Constructor injection 방식은 생성자를 통해서 의존관계를 주입하면 프로그램 실행 시점에서 BeanCurrentlyInCreationException이라는 컴파일 에러가 발생하여 문제를 파악할 수 있다. 반면, 다른 의존성 주입 방식들은 빈이 생성된 후에 참조를 하기 때문에 애플리케이션이 오류나 경고 없이 구동된다. 이후 프로그램이 실행을 시작하였을 때 런타임 에러가 발생한다. 

@Service
publiv class ServiceA{
  @Autowired
  private ServiceB serviceB;
 
  public void test(){
   serviceB.test(); //A가 B의 메서드 호출 
  }
}
@Service
publiv class ServiceB{
  @Autowired
  private ServiceA serviceA;
 
  public void test(){
   serviceA.test(); //B가 A의 메서드 호출 
  }
}

 

4. final 키워드 사용 가능  

 Constructor injection 방식을 이용하면 필드 객체에 final 키워드를 사용할 수 있다. 따라서 컴파일 시점에 누락된 의존성을 확인할 수 있다. 반면, 다른 의존성 주입 방식들은 객체의 생성 이후에 호출되므로 final 키워드를 사용할 수 없다. 

 


참고자료

https://cheershennah.tistory.com/227

https://dev-coco.tistory.com/70

https://jindory.tistory.com/entry/Spring-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-3%EA%B0%80%EC%A7%80-%EB%B0%A9%EC%8B%9D-%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85-%EC%88%98%EC%A0%95%EC%9E%90-%EC%A3%BC%EC%9E%85-%ED%95%84%EB%93%9C-%EC%A3%BC%EC%9E%85