[Spring - JAVA] 의존성 주입 관계

2023. 10. 21. 13:59Spring

오늘 문득 강의를 듣다 내가 이걸 알고 듣는게 맞는가에 대해 고민하기 생각했다. 표면상으로 나름 난 이 개념에 대해 대략 알고 있어라는 생각은 하고 있었지만 막상 설명을 해볼려고 하니 난 이걸 모르고 있던게 맞는거 같다 헣..

 

의존성을 주입을 하는 방법은 3가지 있다

 

  1. 필드 주입
  2. 생성자 주입 👍
  3. 수정자 수입
필드 주입

필드 주입은 객체의 생성 시점과 의존성 주입 시점이 분리가 된다.

필드의 결합도 가 높으며 @Autowried 어노테이션을 통해 직접 지정하고 자동으로 Spring이 자동으로 의존성을 주입한다. 때문에 테스트 코드를 작성해야되는 상황에서는 유연성이 부족한 문제가 있다

객체가 생성된 이후 의존성을 주입하는 방식으로 진행되고 때문에 객체가 생성되는 시점에는 의존성이 초기화 되지 않는다. 때문에 프로그래머가 객체 내부 필드에 접근하려는 순간 순환 잠조 문제가 발생할 수 있다.

 

Ex) 필드 주입 예시

 

객체 A가 필드 주입을 통해 객체 B를 주입받습는다

객체 B도 필드 주입을 통해 객체 A를 주입받습니다

객체 A를 생성하려면 객체 B를 생성하게 되는데 이러한 경우 객체 B 또한 객체 A를 생성하는 경우가 발생한다.

이러한 순환 참조 오류는 객체 생성 시점과 의존성 주입 시점이 다르기 때문에 발생하는 오류이다

 

@Service
public class A {
	private final B, b;
    
    @Autowired
    public A(B b) {
    	this.b = b;
    }
    
    public void doA() {
    	System.out.println("A 객체 실행");
    }
}

@Service
public class B {
	private final A, a;
    
    @Autowired
    public B(A a) {
    	this.a = a;
    }
    
    public void doB() {
    	System.out.println("B 객체 실행");
    }
}

 

해당 코드는 클래스 A, B가 서로 생성자 주입을 통해 의존하고 있고 이러한 구조로 인해 스프링 컨테이너에서는 초기화될 때 순환 참조의 오류가 발생하게 되고 애플리케이션은 실행 중지가 됩니다.

 

이러한 오류를 예방하기 위해서 생성자 주입을 사용하도록 하자

 

생성자 주입

생성자 주입은 객체의 생성과 의존성 주입 시점이 하나로 통합된다

생성자를 통해 의존성이 초기화되기 때문에 객체가 생성된 후에는 필드나 의존성이 초기화되지 않는 상태로 남지 않는다 

이를 통해 순환 참조 문제를 예방하게 되는데 객체 생성 중 순환 참조 문제가 발생하게 되는 경우 프로그램 자체가 실행되지 않는다 이러한 방식이 애플리케이션의 안정성을 높이고 디버깅의 용의함을 제공하게 된다.

 

Ex) 생성자 주입 예시

 

객체 A가 생성자 주입을 통해 객체 B를 주입받습니다

객체 B가 생성자 주입을 통해 객체 A를 주입받습니다

객체 A가 생성하려면 객체 B생성하여야하고 B객체 또한 A객체를 생성해야되는데 이러한 순환 의존성 문제는 생성 단계에 감지되어 객체 생성 자체를 중지한다.

 

이러한 이론적 개념보다는 예시를 통해 보는 것이 좋을 것 같다. 김영한님의 스프링 핵심 원리 - 기본편 중 다양한 의존관계 주입 방법의 커뮤니티 답변 중 하나로 이해한 부분을 바탕으로 설명해보도록 하겠습니다.

 

예시

애플리케이션 == 연극 

빈들을 주입받아 어떤 중요한 기능을 수행하는 객체(이것도 빈입니다.) == 장면 

주입하는 인터페이스 == 역할 

주입하는 실제 구현체(빈) == 실제 배우 

캐스팅 디렉터 == AppConfig

 

의존성 주입 연극

 

연극에는 여러 장면(객체)들이 있을 것이고 이러한 장면들의 연속된 것들을 합쳐 연극이라는 무대를 만듭니다. 그리고 이러한 연극 무대마다의 장면에는 각각의 특정 역할(인터페이스)들이 나올 것이고 각 장면에서 연극에 필요한 역할을 직접 구현하지 않고 실제 배우(실제 구현체 빈)에게 맡깁니다. 또한 연극의 감독은 배우들에게 장면에 할당하는 작업을 수행하며 그 감독이 바로 AppConfig 클래스 입니다.

 

이 연극에서 필드 주입은 각 장면(빈)이 배우(실제 구현)를 필도로 주입받는 것과 유사합니다. 그러면 이 연극의 배우들은 장면이 만들어지자마자 필요한 역할(인터페이스)을 받아들이게 됩니다. 이러한 방식은 배우가 어떤 역할을 맡아야하는지 장면에서 직접 결정하게됩니다. 하지만 이것은 실제 배우가 한 장면이 아닌 여러 장면을 모두 다 안다는 것을 말하며 이로인해 실제 배우와 장면 사이에는 순환 참조 오류의 위험이 발생할 수 있다 -> 캐스팅 디렉터와 배우 간의 양방향 의존성 위험이 있습니다.

 

생성자 주입 연극

 

반면 생성자 주입은 연극의 감독(캐스팅 디렉터)이 배우들을 배역에 할당할 때, 배우가 필요한 역할(인터페이스)을 연극의 감독에게 알려주는 것과 유사합니다. 배우(실제 구현)은 필요한 역할(인터페이스)을 감독(캐스팅 디렉터)에게 말해주고, 감독은 이 역할(인터페이스)에 맞는 배우를 선택하여 배역을 할당합니다. 

이 방식에서 배우(실제 구현)는 어떤 장면(빈)에 할당되든 상관없이 필요한 역할(인터페이스)을 요청하고 받아들입니다. 배우는 장면이 어떤 것인지 신경쓰지 않으며, 그 결과 배우와 장면 사이에는 양방향 의존성이 없어집니다. 이렇게 해서 순환 참조 문제를 예방할 수 있습니다.

 

생성자를 주입하는 경우에는 배우가 역할을 수행하는 장면을 알고 있다면, 각 장면이 어떤 역할을 수행해야 하는지 알고 있어야 합니다. 이것이 하나의 실제 배우가 연극 내의 다양한 장면을 알고 있어야되는 것을 말하며 역할은 배우에게 알려져있지만 역할은 어떤 장면에서 수행하는지는 알지 못하는 관계가 일반적인 의존성 주입이라고 볼 수 있습니다

 

하지만 실제 배우가 어떤 장면에서 어떤 역할을 수행하는지 알고 있다면 그것은 양방향 의존성을 형성하게 된다. 배우와 장면이 서로에 대한 정보를 알고 있기 때문에 일반적으로는 장면은 어떤 배우가 역할을 담당하는지 알면 알필요가 없다는 것을 알아야한다.

 

장면 -> 역할 -> 배우 순으로 이어지는 것이 옳바른 관계라고 볼 수 있다.