의존성 주입 방법들

- 생성자 주입

 

가장 많이 보게 되는 생성자 주입방식이다.

생성자를 통해 객체를 주입해주며 Spring 프레임워크 자체에서도 생성자주입을 권장하기 때문에

생성자가 하나만 존재한다면 자동으로 주입을 해준다.

만약 생성자가 여러개인 경우에는 빈 등록에 쓰일 생성자에 @Autowired를 붙여주어야 한다.

 

- 수정자(Setter) 주입

 

다음은 수정자 setter를 만들어주어 주입을 해주는 방식이다.

변동가능성이 있기 때문에 객체에 final이 빠졌고 스프링 초기에는 자바 기본 스펙이 get, set 이었으므로 수정자 주입을 많이 사용했다고 한다.

객체를 변경할 일이 혹시라도 있으면 수정자 주입을 사용해야할 것이다.

 

- 필드 주입

 

마지막으로 그냥 알아서 때려박아달라는 필드 주입이다. 이것도 final 사용이 불가능하고 아예 추천하지를 않는단다.

필드 주입은 Spring이 알아서 해주기 때문에 외부에서 수정이 불가능므로 이는 테스트 코드에 부적합한 방식이다.

또한 프레임워크에 굉장히 의존적이므로 별루다.

 

- 생성자 주입의 장점

1. 객체의 불변성 확보

수정자 주입과 필드 주입은 일단 final 사용이 불가능하므로 단일 객체를 보장할 수 없고, 변경가능성이 존재한다.(수정자는 게다가 public...)

정말 필요한 경우가 아니라면 의존 관계가 변할 일은 거의 없으므로 생성자 주입을 통해 불변성을 보장하는 것이 유리하다.

 

2. 테스트 코드 작성의 편리함

필드 주입은 @Autowired가 필요하기 때문에 Spring 프레임워크에 의존적이다. (@Autowired없이는 아예 null이 뜬다.)

테스트 코드는 순수 자바로 작성하는 것이 좋으므로 생성자 주입이 유리하다.

 

3. 순환 참조 에러 방지

UserService가 PostService에 의존하고  PostService가 UserService에 의존하는 것을 순환 참조라고한다.

Bean으로 등록되기 위해 서로가 서로를 필요로 하는 Deadlock같은 상황이 벌어진다.

@Autowired는 모든 객체가 생성된 후에 의존관계 주입이 처리되기 때문에 어플리케이션이 일단 실행이 된다.

그 후 상대 객체를 통해 호출하는 메소드들이 계속 쌓이면서 stack overflow가 발생하게 된다.

그에 비해 생성자 주입은 객체의 생성과 의존관계 주입이 동시에 일어나기 때문에 compile 상황에서 미리 순환 참조를 확인할 수 있다.

 

Reference

https://velog.io/@mangtaeeee/Spring-%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0

 

https://mangkyu.tistory.com/125

 

'Spring' 카테고리의 다른 글

[Spring] DI(의존성 주입)는 무엇일까  (0) 2023.12.12

Spring과 JPA를 활용해 처음 개발을 해 가면서, 문득 내가 CRUD를 작동하게는 하지만 왜 이런 코드를 작성했는가에 대해서는 모르는 것도 많고 알아도 얕게 알아서 조리있게 답변하지 못하는 내용들이 많은 것을 깨달았다.

Spring의 핵심 원리인 IOC라는 설계원칙, 이를 충족시키는 DI라는 디자인 패턴, 그리고 생성자를 통한 DI를 권장하는 이유에 대해서 정리해 보려 한다.

강한 결합

DI가 필요한 이유를 이해하기 위해선 먼저 강한 결합을 가지는 클래스의 관계에 대해 생각해 보아야 한다.

 

public class Customer {

	private Chicken chicken;
    
    public Customer() {
    	this.chicken = new Chicken();
    }
}

 

고객이라는 클래스는 치킨이라는 음식을 먹을 수 있다.

그러나 이러한 클래스설계는 두 클래스가 강하게 결합되어 있다는 단점을 가진다.

고객 클래스는 먹을 수 있는 메뉴가 치킨 뿐이다. 만약 다른 음식을 먹고 싶다면, Chicken이라는 필드값을 Pizza로 수정해야 하는 수고로움이 있다.

즉 Customer 클래스는 Chicken 클래스와 강하게 결합되어 있어 다른 음식으로 교체만 하고 싶음에도 Customer 클래스 자체를 수정해야하는 번거로움이 있다.

이는 Customer 클래스가 Chicken이라는 클래스에 '의존' 하고 있기 때문이다.(Customer가 Chicken을 사용하고 있는 것 = Customer가 Chicken에게 의존)

 

Dependency Injection(의존성 주입)

이제 의존성 주입을 통해서 강한 결합을 느슨하게 만들어보자

일단 위 클래스에서 가장 문제가 되는 것은 Customer가 다른 음식을 먹고 싶을 때마다 Customer 클래스의 필드까지 수정해주어야 하는 번거로움이 있다는 것이다.

이 자체는 객체지향의 특징 다형성을 통해서 해소가 가능하다.

 

먼저 Food라는 interface를 선언해주고 Chicken이 Food를 Implements 하게 한다면

public class Customer {

	private Food food;
    
    public Customer(Food food) {
    	this.food = food;
    }
}

 

이런 식의 클래스 형태가 만들어진다.

그러면 나중에 Customer의 생성자 인자에 원하는 Food를 갈아끼워주기만 하면 된다.

이러면 클래스를 수정할 필요가 없으므로 편의성과 다른 Food들에 대한 확장성, 유연성도 좋아진다.

 


//기존 Customer 사용 방법 Chicken 클래스에게만 의존이 가능

Customer customer = new Customer();

//수정 후 Customer 사용 방법

Food food = new Pizza();

Customer customer = new Customer(food);

 

이제 우리는 Food 인터페이스에 구체 클래스를 확정하고, Customer 클래스가 무엇을 의존할지 '의존성을 주입' 해주면서 강한 결합을 느슨하게 만들어 주었다.

여기서 나오는 것이 IOC의 개념이다.

DI를 하지 않은 설계에서는  

Customer -> Chicken 이라는 구체 클래스로 제어가 진행되었다면

DI를 한 설계에서는

Food를 확정한 후 -> Customer에 주입하는 식으로 제어의 역전이 일어난 것이다.

ex) DI를 통해 Repository -> Service -> Controller 로 제어의 역전.

 

즉 DI라는 디자인 패턴을 통해 IOC라는 설계 원칙을 충족한 것이다.

 

Spring에서는 DI 컨테이너를 제공하여 application 실행 시점에 Bean을 생성하고

특정 annotation이 붙은 클래스들을 탐색하며 의존성 주입들을 알아서 쫙쫙해준다!

DI 컨테이너가 Bean들의 생명주기와 객체 관계를 알아서 해주는 것도 IOC라고 볼 수 있다.

(우리가 직접 지정해주던 관계 -> JVM, 에서 Spring이 정해주는 관계와 Bean, 생명주기 -> 우리가 사용)

 

DI의 장점

강한 결합을 느슨하게 만들어줘 확장성과 유연성을 보장한다!!

+ Recent posts