의존성 주입 (DI)

주현·2023년 11월 8일
0

springboot

목록 보기
4/8

의존성 주입(DI)이란?

회사에서 사용하는 애플리케이션은 하나의 객체로 이루어져 있지 않습니다.
복잡한게 아닌 간단한 애플리케이션도 여러개의 객체가 서로 협력하고 있습니다.

객체가 협력한다는 것 => 객체 간의 의존성이 존재한다는 것
의존성이란 파라미터나 리턴값 또는 지역변수 등으로 다른 객체를 참조하는 것을 의미한다.
이렇게 하나의 객체는 다른 객체의 부품이 된다.

Spring에서 관리해주는 객체를 Spring Bean이라고 하는데, 주로 Controller, Service, Repository를 스프링 컨테이너에서 관리하게 된다. 따라서 의존성 주입도 스프링 컨테이너에서 관리하는 객체들을 주입하는 과정이라고 생각하면 편하다.


의존성 주입이 필요한 이유

예를 들어서 pencil이라는 상품, 연필을 판매하는 Store 클래스가 있습니다.

public class Store {

    private Pencil pencil;

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

}

위 예시 클래스는 두가지의 문제점을 가지고 있습니다.

  • 두 클래스가 강하게 결합되어 있습니다.
  • 객체들 간의 관계가 아닌-> 클래스 간의 관계가 맺어졌습니다.

1. 두 클래스가 강하게 결합되어 있음.

위 예는 Store 클래스는 현재 Pencil클래스와 강하게 결합되어 있습니다.
만약 storedptj pencil이 아닌 다른 것을 판매한다고 했을때, Store 클래스에 생성자 변경이 필요하다.-> 유연성이 떨어집니다.
각기 다른 상품들을 판매하기 위해 생성자만 다르고 나머지는 중복되는 Store 클래스들이 파생되는 것은 좋지 못하다.
이에 해결책으로 상속? 상속은 제약이 많고 확장성이 떨어지므로 피하는 것이 좋다고 한다.

2.객체들 간 관계가 아닌 클래스 간 관계가 맺어짐

위 예에서 store,pencil은 객체들 간의 관계가 아닌 클래스들 간의 관계가 맺어져 있다는 문제가 있습니다. 객체지향적 설계를 위해서라면 클래스가 아닌 객체간의 관계가 맺어져야 합니다. 객체들 간 관계가 맺어져있다면, 다른 객체의 구체를 알지 못해도, 인터페이스 타입으로 사용할 수 있습니다.

->spring에서는 DI를 적용하여 이러한 문제를 해결하고자 하였음.


의존성 주입 정리

  • 두 객체 간의 관계라는 관심사의 분리
  • 두 객체 간의 결합도를 낮춤
  • 객체의 유연성을 높임.

의존성 주입 방법 3가지

✔️필드 주입

변수에 @Autowired 어노테이션을 붙이게 되면 스프링 프레임워크에서 해당 객체를 자동으로 스프링 빈으로 등록한다.

<코드>

@RestController
public class FieldController{

	@Autowired
    private MyService myService;
    

스프링 컨테이너와 높은 결합도를 가지게 된다.

@Autowired 어노테이션의 동작 방식은 스프링 컨테이너에 등록된 빈을 찾아 해당 객체에 직접 주입시키는 방식이다. But,이 방식은 스프링 컨테이너 외에서는 실행할 수 없기 때문에 스프링 컨테이너와의 높은 결합도를 가지게 된다는 것이다.

불변 상태로 생성할 수 없다.

마찬가지로 스프링 컨테이너에서 등록된 빈을 찾아 직접 주입해야 하기 때문에 final 키워드를 사용할 수 없다. 따라서 해당 객체는 언제든지 변할 수 있는 mutable한 객체다.


✔️Setter 메서드를 통한 의존성 주입

<코드>

@RestContoller
public class SetterController{
	
    private MyService myService;
    
    @Autowired
    public void setMyService(MyService myService){
    	this.myService = myService;
    }
    
}  

불변 상태로 생성할 수 없다.

Setter Injection의 경우 Field Injection과 마찬가지로 관계를 주입받는 객체의 변경 가능성이 열려있다. 이런 이유 때문에 Setter Injection 방식은 주입 받는 객체가 변경될 필요성이 있을 때만 사용해야 한다.


✔️생성자 주입

<코드>

@RestContoller
public class DIController{
	
    private MyService myService;
    
    @Autowired
    public DIController(MyService myService){
    	this.myService = myService;
    }
    
    @GetMapping("/di/hello")
    public String getHello(){
    	return myService.getHello();
    }
}  
  • 생성자 주입은 생성자의 호출 시점에 1회 호출 되는 것이 보장된다. 그렇기 때문에 주입받은 객체가 변하지 않거나, 반드시 객체의 주입이 필요한 경우에 강제하기 위해 사용할 수 있다
  • 생성자 주입 방식을 사용하면 위의 문제들을 해결하고 장점을 작용할 수 있는 몇 가지의 기능을 더 제공해줍니다.

Final 키워드 사용 가능

생성자 주입은 생성자라는 키워드 특성상 필드를 final로 선언하여 불변한 객체로 만들 수 있다. 또한 스프링 프레임워크에서 단 한번만 주입 받는 것이 보장됩니다.

lombok과 사용
@RestController를 사용하여 해당 클래스를 빈으로 등록하고, @RequiredArgsConstructor의 룸북 어노테이션을 붙여서 쓰면 final 이 붙어있는 필드에 대해서 생성자를 따로 만들어주기에 따로 생성자를 만들 필요가 없다. 그렇기에 아래와 같은 코드로 작성할 수 있음.

<코드>

@RestController
@RequiredArgsConstructor
public class SetterController{
	
    private final MyService myService;
    
}  

순환 참조 방지

순환 참조 에러는 A객체가 B객체를 참조하고, B객체가 A객체를 서로 참조하고 있을 때 발생합니다. 이렇게 한다면, 순환참조 때문인지 다른 문제 때문인지도 판단 하는데 시간도 걸릴 것이다.
그렇기 때문에 생성자 주입 방식을 사용하면 위와 같은 문제를 해결할 수 있다.
같은 코드지만 의존성 주입 방식만 바꿨다. 이렇게 생성자 주입을 사용한다면 컴파일 타임에 아래와 같은 에러 메시지가 출력된다.


생성자 주입을 사용해야 하는 이유

  • 객체의 불변성 확보
  • 테스트 코드의 작성
  • final 키워드,Lombok과의 결합
  • 순환 참조 에러 방지
profile
Just fucking do it!! 개발자가 꿈인 25살 학부생입니다!!

0개의 댓글