DI

Haechan Kim·2023년 1월 5일
0

Spring

목록 보기
20/68
post-thumbnail

Dependency Injection (의존성 주입)
의존관계란? A가 B를 의존한다 -> B가 변하면 A에 영향을 미친다.
의존관계를 인터페이스로 추상화 -> 다양한 의존관계, 실제 구현 클래스와 관계 느슨해지고 결합도 낮아짐.

DI : 의존 관계를 외부에서 결정하고 주입하는 것. ¹객체 직접 생성 x, ²외부에서 생성 후 주입.

다음 세가지 조건을 충족하는 작업을 의존관계 주입 이라고 부름.

  • 클래스나 코드에서 런타임 시점에 의존관계 드러나지 않음. 즉 인터페이스만 의존한 경우.
  • 런타임 시점의 의존관계는 제 3의 존재가 결정(컨테이너..)
  • 의존관계는 외부에서 주입(제공).

객체를 주입 받으면 결합도 낮추고, 런타임 시 의존관계 결정되기 때문에 유연한 구조.

의존성 주입 방식

  1. 필드 주입 (Field Injection)
    @Autowired 사용한 필드 주입
@Controller
public class Controller {
	@Autowired
	private Service service;
}

인텔리제이 필드 주입 시 나오는 경고 -> Field injection is not recommended... Always use constructor based dependency injection in your beans.

  • 의존 관계 파악 힘듦
  • 순환참조 방지 안되는 문제 -> Spring Boot 2.6에서 패치 되어 에러 뜬다고 함!
  • 편하다
  1. 수정자 주입 (Setter Injection)
@Controller
public class Controller {
	private Service service;
    
    @Autowired
    public setService(Service service) {
    	this.service = service;
    }
    
    public void callService() {
    	service.doSth();
    }
}
public interface Service {
    void doSomething();
}
public class ServiceImpl implements Servce {
	@Override
    public doSth() {
    	System.out.println("do sth.");
    }
}
public class Main {
    public static void main(String[] args) {
        Controller controller = new Controller();

        // Service 인터페이스 구현하기만 하면 된다.
        controller.setService(new ServiceImpl1());
        controller.setService(new ServiceImpl2());

        controller.setService(new Service() {
            @Override
            public void doSomething() {
                System.out.println("Anonymous class is doing sth.");
            }
        });

        // 어떻게든 구현체를 주입하고 호출하면 됨.
        controller.callService();
    }
}
  • setter 메서드에 @Autowired를 붙임
  1. 생성자 주입 (Constructor Injection)
@Controller
public class Controller {
	private final Service service;
    
    public Controller(Service service) {
    	this.service = service;
    }
    
    public void callService() {
    	service.doSth();
    }
}
public class Main {
    public static void main(String[] args) {

        // Controller controller = new Controller(); // 컴파일 에러

        Controller controller1 = new Controller(new ServiceImpl());
        Controller controller2 = new Controller(
            () -> System.out.println("Lambda implementation is doing something")
        );
        Controller controller3 = new Controller(new Service() {
            @Override
            public void doSomething() {
                System.out.println("Anonymous class is doing something");
            }
        });

        controller1.callService();
        controller2.callService();
        controller3.callService();
    }
}
  • final 키워드 선언 가능. 다른 주입은 x

-> 생성자 주입 권장 이유?

  1. 순환 참조 방지 (감지 가능)
    A가 B를 참조하면서, B가 A를 참조하는 경우
    필드 주입의 경우 실행 잘 됨. 생성자 주입 시 에러 발행.
    생성자 주입은 필드/수정자 주입과 빈 주입 순서가 다르기 때문!

필드/수정자 주입은 먼저 빈 생성 후, 주입하려는 빈 찾아서 주입함.
but 생성자 주입은 먼저 생성자의 인자에 사용되는 빈 찾거나, 빈 팩토리에서 빈 생성. 그리고 나서 주입하려는 빈 생성자 호출.
즉 먼저 빈 생성하는게 아니라 주입하려는 빈 먼저 생성.

ex) A 클래스가 B 클래스 의존, B 클래스가 C 클래스 의존.
A에 대한 빈 만들기 위해서 B를 주입해야 함.
B의 빈 주입해야 하는데 없으니까 B의 빈 생성. 근데 C의 빈 없으니까 C의 빈 생성.
즉 스프링은 C-B-A 순으로 빈 생성.

A->B, B->A 인 상황이면 A의 빈 만들때 B의 빈 주입해야 하는데, 없으니까 B의 빈 먼저 생성. 이때 A의 빈 주입해야 하는데 없으니까 A의 빈 먼저 생성. 이때 B의 빈 없으니까...무한 반복.
그렇기 때문에 생성자 주입 사용하면 스프링 애플리케이션 로딩 시점에 예외 발생!

  1. NullPointerException 방지
    수정자 주입 시 Service 인터페이스 구현하기만 하면 어떤 객체라도 Controller에서 사용 가능(다형성). Controller는 해당 구현체 내부 아무것도 모름.
    어떤 구현체든 Service 인터페이스 구현하기만 하면 됨. -> 문제는 수정자 통해서 구현체 주입 안해도 Controller는 객체 생성이 가능함. 그러면 내부에 있는 callService()도 가능. -> doSth() 호출 시 NPE 발생.

주입 필요한 객체 주입 안되어도 객체 생성 가능한 것이 문제.

생성자 주입 사용시 의존관계 주입을 하지 않은 경우에는 Controller 객체를 생성할 수 없음.
의존관계 내용을 외부로 노출시킴으로써 컴파일 타임에 오류 잡을 수 있음.

  1. 테스트 코드 작성하기 좋다
    필드 인젝션 사용해 작성한 클래스라면 단위 테스트 시 읮노관계 갖는 객체 생성해 주입할 수 없음. IoC 컨테이너가 다 생성해서 주입 -> 외부로 노출되어 있는것 아무것도 없기 때문. NPE 발생.
    생성자 주입 사용해 작성된 클래스는 객체 생성 시 원하는 구현체 넘겨주면 됨. (구현체 넘기지 않으면 객체 생성 자체가 불가능)

<참고>
생성자 주입 사용 이유 : https://yaboong.github.io/spring/2019/08/29/why-field-injection-is-bad/
https://n1tjrgns.tistory.com/230
https://jackjeong.tistory.com/41
전략패턴 : https://victorydntmd.tistory.com/292
DI, SOLID : https://imspear.tistory.com/153
순환 참조 : https://ch4njun.tistory.com/269

<생각해보기>

  • 생성자 주입을 권장하는 이유는?
  • 생성자 주입 사용은 SOLID 원칙 중 어떤 원칙을 지킬 수 있을까?
    ㄴ OCP, DIP
  • 생성자 주입과 필드 주입의 순환 참조 처리 차이와 그 이유는?
  • 수정자 주입을 사용하면 왜 NPE이 발생할까?

0개의 댓글