DI 의존성 주입 방법 3가지 + 생성자를 권장하는 이유

코딩을 합시다·2023년 3월 28일
0

Spring은 @Autowired 어노테이션을 이용한 다양한 의존성 주입(DI; Dependency Injection) 방법을 제공합니다.

의존성 주입은 필요한 객체를 직접 생성하는 것이 아닌 외부로부터 객체를 받아 사용하는 것입니다.

  • 이를 통해 객체간의 결합도를 줄이고 코드의 재활용성을 높일 수 있습니다.
  • @Autowired 는 Spring에게 의존성을 주입하는 지시자 역할로 쓰입니다.

의존성 주입의 3가지 방법
1. 생성자 주입(Constructor Injection)
2. 필드 주입(Field Injection)
3. 수정자 주입(Setter Injection)


  1. 생성자 주입(Constructor Injection)
@RestController
public class HelloController {

    private HelloRepository helloRepository;

    @Autowired
    public HelloController(HelloRepository helloRepository) {
        this.helloRepository = helloRepository;
    }
    
    // 생성자 호출 시점에 1번만 호출 → 불변하게 설계 가능
    // 객체들을 final로 선언할 수 있다는 장점이 있음
    // 생성자가 하나일 경우 @Autowired를 생략할 수 있다.
}
  1. 필드 주입(Field Injection)
@RestController
public class HelloController {

    @Autowired
    private HelloRepository helloRepository;
}
  1. 수정자 주입(Setter Injection)
@RestController
public class HelloController {

    private HelloRepository helloRepository;

    @Autowired
    public void setHelloRepository(HelloRepository helloRepository) {
        this.helloRepository = helloRepository;
    }
    
    // 단점 : 수정자 주입을 사용하면 setXXX 메서드를 public으로 열어두어야 하기 때문에 언제 어디서든 변경이 가능하다.
}

어떤 주입 방식을 사용하는게 좋을까?

Spring Framwork reference에서 권장하는 방법은 생성자를 통한 주입입니다.
@Autowired 어노테이션만으로 간단하게 의존성을 주입할 수 있는데 왜 생성자 주입 방법을
권장하는걸까요?
필드 주입이나 수정자 주입과 다르게 생성자 주입 방법이 주는 장점에 대해 알아보도록 하겠습니다.

생성자 주입을 권장하는 이유


1. 순환 참조를 방지할 수 있다.

  • 개발을 하다 보면 여러 컴포넌트 간에 의존성이 생깁니다.
  • 예를 들어, A가 B를 참조하고, B가 다시 A를 참조하는 순환 참조되는 코드가 있다고 가정해 봅니다.
@Service
public class AService {
 
    // 순환 참조
    @Autowired
    private BService bService;
 
    public void HelloA() {
        bService.HelloB();
    }
}
@Service
public class BService {
    
    // 순환 참조
    @Autowired
    private AService aService;
 
    public void HelloB() {
        aService.HelloA();
    }
}

필드 주입과 수정자 주입은 빈이 생성된 후에 참조를 하기 때문에 어플리케이션이 아무런 오류 그리고 경고 없이 구동됩니다.
그리고 그것은 실제 코드가 호출될 때까지 문제를 알 수 없다는 것입니다.

반면, 생성자를 통해 주입하고 실행하면 실행이 되지 않고 순환 참조 문제가 있다며 BeanCurrentlyInCreationException 예외가 발생합니다.

즉, 생성자 주입 방식을 사용하면 순환 참조 문제를 해결하는 것이 아니라, 순환 참조 문제를 애플리케이션 실행 시점에 알려줘서, 실제 서비스되기 전에 개발자로 하여금 순환 참조 문제를 해결할 수 있게 해 줍니다.
이러한 이유들로 인해, Spring에서 객체 의존성 주입은 생성자 주입 방식을 사용하는 것이 권장됩니다.

잠깐!

[ * 스프링 부트 2.6 버전 이후로는 스프링을 구동할 때 순환참조에 대한 이슈가 발생하면 구동자체를 못시키게 하는 업데이트 내역이 존재해서 필드 주입과 수정자 주입도 오류를 발생시킨다 하지만 스프링부트 2.6 미만의 기존 레거시 프로그램에서는 필드 주입과 수정자 주입의 순환참조 오류를 찾아내지 못하고 애플리케이션을 실행시키기 때문에 실제 코드가 호출될 때까지 개발자가 오류를 발견할 수 없다. * ]

하지만 생성자 주입은 스프링 부트의 버전과 상관없이 순환참조에 대한 오류를 주고 있기 때문에 애플리케이션을 실행시키지 않습니다.


2. 개발자의 의존성 주입 실수 방지 (final 키워드의 사용)


3. 테스트에 용이하다.
생성자 주입을 사용하게 되면 테스트 코드를 좀 더 편리하게 작성할 수 있습니다.

  • 독립적으로 인스턴스화가 가능한 POJO(Plain Old Java Object)를 사용하면, DI 컨테이너 없이도 의존성을 주입하여 사용할 수 있습니다.

  • 이를 통해 코드 가독성이 높아지며, 유지보수가 용이하고 테스트의 격리성과 예측 가능성을 높일 수 있다는 장점이 생기게 됩니다.

  • 위와 같은 이유로 필드 주입이나 수정자 주입 보다는 생성자 주입의 사용을 권장합니다.

public class CocoService {
    private final CocoRepository cocoRepository;
    
    public CocoService(CocoRepository cocoRepository) {
    	this.cocoRepository = cocoRepository;
    }
    
    public void doSomething() {
        // do Something with cocoRepository
    }
}
 
public class CocoServiceTest {
    CocoRepository cocoRepository = new CocoRepository();
    CocoService cocoService = new CocoService(cocoRepository);
    
    @Test
    public void testDoSomething() {
        cocoService.doSomething();
    }
}

4. 객체의 불변성 확보

생성자로 의존성을 주입할 때 final로 선언할 수 있고, 이로인해 런타임에서 의존성을 주입받는 객체가 변할 일이 없어지게 됩니다.(필드 주입과 수정자 주입은 final을 선언할 수 없음)

하지만 수정자 주입이나 일반 메소드 주입을 이용하게되면 불필요하게 수정의 가능성을 열어두게 되고,
이는 OOP의 5가지 원칙 중 OCP(Open-Closed Principal, 개방-폐쇄의 원칙)를 위반하게 됩니다.

그러므로 생성자 주입을 통해 변경의 가능성을 배제하고 불변성을 보장하는 것이 좋습니다.
또한, 필드 주입 방식은 null이 만들어질 가능성이 있는데, final로 선언한 생성자 주입 방식은 null이 불가능합니다.


출처

0개의 댓글