코딩항해기

[Spring] xml Bean을 통한 의존성 주입 (CI, SI) 본문

Spring

[Spring] xml Bean을 통한 의존성 주입 (CI, SI)

miniBcake 2024. 10. 2. 12:41

 

 

의존성 주입 DI (Dependency Injection)

Spring 컨테이너에서 객체 Bean을 먼저 생성해두고 생성한 객체를 지정한 객체에 주입하는 방식을 의존성 주입이라고 한다.

기존에는 new를 사용해 개발자가 직접 관리했지만 Spring에서는 객체 관리를 Spring 컨테이너가 진행한다. 따라서 직접 new를 사용하게 되면 컨테이너의 관리를 받을 수 없고 의존적이라고 볼 수 있다. 의존성 주입을 통해 객체 간의 의존성을 줄여줄 수 있으며 유연하고 확장성이 뛰어난 코드 작성이 가능해진다.

 

의존성 주입 방식의 장점

  • 재사용성을 높여준다.
  • 테스트에 용이하다.
  • 코드를 단순화 시켜준다.
  • 사용하는 이유를 파악하기 수월하고 코드가 읽기 쉬워지는 점이 있다.
  • 종속성이 감소하기 때문에 변경에 민감하지 않다.
  • 결합도(coupling)는 낮추면서 유연성과 확장성은 향상 시킬 수 있다.
  • 객체간의 의존관계를 설정할 수 있다.

 

의존성 주입 방식

의존성을 주입하는 방식에는 필드 주입(FI), setter 주입(SI), 생성자 주입(CI) 방식 있다.

이 중 필드 주입 방식은 가장 간편해보이고 직관적으로 보이지만 가장 경계되는 방식으로 명시적으로 드러나지 않는 형태라는 문제와 불변성을 보장 받지 못한다는 문제 등을 가지고 있기 때문이다.

 

생성자 주입

public class GalaxyPhone implements Phone{
    //의존성 주입을 위한 필드 설정
	private Watch watch;
	private int num;

	public GalaxyPhone() {
		System.out.println("갤럭시 객체 생성");
	}

	//생성자 주입을 위한 watch 생성자
	public GalaxyPhone(Watch watch, int num) {
		this.watch = watch;
		this.num = num;
		System.out.println("갤럭시 객체 생성2 워치");
	}
}
//xml파일
<bean class="test.GalaxyWatch" id="galaxyWatch"/>
<bean class="test.GalaxyPhone" id="galaxy">
   <constructor-arg ref="galaxyWatch"/>
   <constructor-arg value="1234"/>
</bean>

 

생성자 주입 방식은 constructor-arg 태그를 사용하며 속성으로 ref(레퍼런스, 객체), value(값)를 사용해 값을 추가한다.

 

실행 순서는 생성자를 호출한 뒤 객체를 생성한다. 이 때 사용할 수 없는 생성자가 없거나, 순서가 다르거나, 값이 부족하거나 등 의존중인 객체가 하나라도 없는 문제가 발생하면 오류가 생기게 되어 Bean 자체가 생성되지 않는다.

 

장점으로는 NullPointerException을 방지하고 컴파일 단계에서 미리 캐치할 수 있도록 하며, 순환 참조도 컴파일 단계에서 확인할 수 있다. final 지시자도 사용이 가능해 불변성을 보장받을 수 있다.

 

 

setter 방식

public class GalaxyPhone implements Phone{
	private Watch watch;//의존성 주입을 위한 필드 설정
	private int num;

	public GalaxyPhone() {
		System.out.println("갤럭시 객체 생성");
	}
	
	//세터
	public void setWatch(Watch watch) {
		this.watch = watch;
	}
	
	public int getNum() {
		return num;
	}
}
//xml
<bean class="test.GalaxyWatch" id="watchName">
	<property name="watchName" value="갤럭시워치"/>
</bean>
<bean class="test.GalaxyPhone" id="phoneGalaxyTest">
	<property name="watch" ref="watchName"/>
	<property name="num" value="1234"/>
</bean>

 

setter 주입 방식은 property 태그를 사용하며 속성으로 ref(객체), value(값) 통해 값을 넣을 수 있다.

 

실행 순서는 객체를 먼저 생성한 뒤에 호출하는 setter를 통해 차례로 값을 추가하게 된다. 이미 객체를 생성했기 때문에 setter를 통한 값 주입 과정에서 오류가 발생하더라도 Bean를 반환할 수 있다. 그러나 필드의 final을 사용하지 못하는 문제와 순환 의존성에 관한 문제가 있다.

 

순환 의존성
순환 의존이란 서로 다른 클래스가 서로 참조하게 되는 상황으로 객체가 서로의 객체를 이용하기 되면 서로의 객체가 생성되지 않아 콜스택이 쌓이다 오버플로우가 발생한다. 

 

 

생성자 주입 방식과 setter 주입 방식

생성자 주입 방식은 오류가 발생하면 생성되지 않기 때문에 주입해야하는 데이터가 핵심이거나 하나라도 주입 받지 않으면 안될 때 사용할 수 있다. 사용자 입장에서는 다소 불편한 방식이지만 제한적이고 고딕한 상황일 때 사용하면 용이하다.

 

반면 setter 주입 방식은 생성 후에 setter를 통해 주입하기 때문에 보편적으로 더 많이 사용한다. 동물의 숲으로 예시를 들면 잠자리 채가 없어도 주민은 있을 수 있기 때문이다.

 

즉, 의존성 주입 방식에는 웹에서 잘 사용하지 않는 생성자 주입과 보편적으로 사용되는 setter 주입이 있다. 생성자는 하나라도 오류가 생기면 생성되지 않고 오류가 발생하므로, 사용자 불편이 크므로 보편적으로 setter 주입을 사용한다.