[Spring] Dependency Injection (DI)

Coastby·2022년 10월 10일
0

LIKELION Back-End School

목록 보기
36/61

DI란 Dependency Injection의 줄임말로, 의존성 주입이다.
Spring 프레임워크는 3가지 핵심 프로그래밍 모델을 지원하는데, 그 중 하나가 의존성 주입(DI)이다. DI란 외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴으로, 느슨한 결합을 하게 해준다.

"A가 B를 의존한다." 의 의미는 다음과 같다.

  • 의존대상 B가 변하면, 그것이 A에 영향을 미친다.
  • 즉, B의 기능이 추가 또는 변경되면 그 영향이 A에 미친다.

강한 결합 vs 느슨한 결합

1) 강한 결합

객체 내부에서 다른 객체를 생성하는 것은 강한 결합도를 가지는 구조이다.

  • A클래스 내부에서 B라는 객체를 직접 생성하고 있다면, B객체를 C객체로 바꾸고 싶은 경우에 A클래스도 수정해야 한다. 상속을 생각할 수 있지만 상속은 제약이 많고 확장성이 떨어지므로 피하는 것이 좋다.
  • 객체들 간의 관계가 아니라 클래스 간의 관계가 맺어진다.

아래의 예제에서 Pencil을 판매하는 Store가 있다고 할 때, Store에서 다른 Computer와 같은 상품을 판매하기 위해서는 Store 클래스를 수정해야하는 문제점이 있다.

public class Store {
	private Pencil pencil;
    public Store(){
 		this.pencil = new Pencil();
    {
 }

2) 느슨한 결합

객체를 주입 받는다는 것은 외부에서 생성된 객체를 인터페이스를 통해서 넘겨받는 것이다. 이렇게 하면 결합도를 낮출 수 있고, 런타임시에 의존관계가 동적으로 주입되어 유연한 구조를 가진다.

  • 다형성을 이용하여 Product라는 interface를 생성하고, Pencil은 interface를 구현하도록 한다.
public interface Product {}

public class Pencil implements Product {}
public class Computer implements Product {}
  • 위에서 Store와 Pencil이 강하게 결합되어 있는 부분을 interface로 교체해준다.
public class Store {
	private Product product;
    
    public Store(Product product) {
    	this.product = product;
    }

}
  • 위의 클래스에서 interface로 교체하기 위해서는 외부에서 Store 인스턴스를 생성할 때 product에 해당하는 구체 클래스를 생성하여 주입해주어야 한다.
public class Main {
    public static void main(String[] args) {

        // Store store = new Store(); // 컴파일 에러

        Store store1 = new Store(new Pencil());
        Store store2 = new Store(new Computer());

이를 이용하면 Store에서는 상품이 변경되어도 Store 클래스는 수정이 없고, 생성할 때 Product를 구현한 클래스만 주입해주면 된다.

○ DI의 종류

의존성 주입에는 생성자 주입, 필드 주입, 수정자 (setter) 주입 등이 있으며 생성자 주입을 강력히 권장한다.

필드 주입

필드에 @Autowired 어노테이션만 붙여주면 자동을 의존성 주입이 된다. 사용법이 매우 간단하다. 하지만,

  • 코드가 간결하지만, 외부에서 변경하기 힘들다.
  • 프레임워크에 의존적이고 객체지향적으로 좋지 않다.
public class Store {
	@Autowired
	private Product product;

}

수정자 주입

setter 주입은 런타임시에 할 수 있도록 낮은 결합도를 가지게 구현되었다. 하지만,

  • setter를 통해서 구현체(Pencil)를 주입해주지 않아도 Store 객체는 생성이 가능하다.
  • 이 자체도 문제가 되지만, Store 안의 메서드 중 Product를 사용하는 메서드가 있다면 NullPointerException이 발생한다.
  • setter를 public으로 열어두기 때문에 언제 어디서든 변경이 가능하다.
public class Store {
	private Product product;
    
    public void setProduct (Product product) {
    	this.product = product;
    }

}

public class Main {
    public static void main(String[] args) {

        Store store = new Store();
        store.setProduct(new Pencil());
        store.setProduct(new Computer());		//바꿀 수 있다.
        
        Store store2 = new Store();				//그냥 만들 수도 있다.

⭐️ 생성자 주입 ⭐️

생성자 주입의 경우

  1. null을 주입하지 않는 한 NullPointerException은 발생하지 않는다.

  2. 의존관계를 주입하지 않은 경우에는 객체를 생성할 수 없다. 즉, 의존관계를 외부로 노출시킴으로써 컴파일 타임에 오류를 잡아낼 수 있다.

  3. final을 사용할 수 있다. final로 선언된 레퍼런스타입 변수는 반드시 선언과 함께 초기화가 되어야 하므로 setter 주입시에는 의존관계를 주입받을 필드에 final을 사용할 수 없다.

  4. 생성자 주입을 하면 객체 간 순환참조를 하고 있는 경우 스프링 어플리케이션이 구동되지 않는다. 컨테이너가 빈을 생성한느 시점에서 객체생성에 사이클관계가 생기기 때문이다.
    (setter 주입 시 구동은 되지만 순환참조를 하고 있는 부분에 호출이 생기면 StackOverFlowError가 생긴다.)

  5. 테스트 코드 작성하기 좋다.
    (필드 주입 시 객체를 생성해서 주입할 수가 없다.)

참고 : https://yaboong.github.io/spring/2019/08/29/why-field-injection-is-bad/
https://mangkyu.tistory.com/150

profile
훈이야 화이팅

0개의 댓글