본문 바로가기
Backend/Spring Boot

Spring DI

by 이은선 2024. 6. 30.
728x90
SMALL

개념

  • DI란 외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴으로, 인터페이스를 사이에 둬서 클래스 레벨에서는 의존 관계가 고정되지 않도록 하고 런타임 시에 관계를 동적으로 주입하여 유연성을 확보하고 결합도를 낮출 수 있게 해준다.
  • DI는 외부에서 객체(빈)들 간의 의존관계를 결정하고 주입하는 것이다. 즉, 객체를 직접 생성하는 것이 아니라 외부에서 생성 후 주입시켜 주는 방식

 

장점

  • 두 객체 간의 관계라는 관심사의 분리
  • 두 객체 사이의 의존도/결합도를 낮출 수 있다.
  • 유연하고 확장성이 뛰어난 코드 작성이 가능하다.
  • 테스트 작성을 용이하게 한다.
  • 재사용성이 높은 코드가 된다.

 

의존성 주입 방식 (DI의 종류)

1. 필드 주입 (Field Injection)

  • 클래스에 선언된 필드에 생성된 객체를 주입하는 방식
  • 필드에 주입할 때는 스프링에서 제공하는 @Autowired 어노테이션을 주입할 필드 위에 명시해준다.
  • 간편하게 의존 관계 주입이 가능하지만 참조 관계를 눈으로 확인하기 어렵고, 순환참조를 막을 수 없다.
  • 생성자 주입을 뺀 나머지(필드 주입, setter 주입)은 모두 생성자 이후에 호출되므로 final 키워드를 사용할 수 없다.
@Component
public class OrderServiceImpl implements OrderService {
	
    @Autowired
	private MemberRepository memberRepository;
    
    @Autowired
    private DiscountPolicy discountPolicy;

}

 

장점

  • 코드가 간결해진다.
  • 사용하기 편하다.

단점

  • DI 프레임워크가 없으면 아무것도 할 수 없다.
  • 외부에서 변경이 불가능해서 테스트하기 어렵다.
  • final 키워드를 사용할 수 없으므로 불변성이 보장되지 않는다. 따라서, 객체가 변할 수 있다.
  • 순환참조가 발생할 수 있다.
  • Solid 원칙 중에 단일 책임 원칙(SRP)을 위반할 가능성이 커진다.
    • @Autowired 선언만 하면 되므로 의존성을 주입하기 쉽다.
    • 따라서, 하나의 클래스가 많은 책임을 갖게 될 가능성이 높다.
  • 의존성이 숨는다.
    • 생성자 주입에 비해 의존 관계를 한 눈에 파악하기 어렵다.

 

2. 수정자 주입 (Setter Based Injection)

  • 클래스의 수정자를 통해서 의존성을 주입해주는 방식
  • 선택, 변경 가능성이 있는 의존관계에 사용
@Component
public class OrderServiceImpl implements OrderService {
	private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;
    
    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
    	this.memberRepository = memberRepository;
    }
    
    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
    	this.discountPolicy = discountPolicy;
    }
}

 

장점

  • 선택적인 의존성을 사용할 수 있다.

단점

  • 선택적인 의존성을 사용할 수 있다는 것은 모든 구현체를 주입하지 않아도 객체를 생성할 수 있고, 객체의 메소드를 호출할 수 있다. 즉, 주입 받지 않은 구현체를 사용하는 메소드에서 NPE가 발생한다.
  • 순환 참조 문제가 발생할 수 있다.

 

3. 생성자 주입 (Constructor Based Injection)

  • 클래스의 생성자를 통해서 의존성을 주입해주는 방식으로 생성자에 @Autowired 어노테이션을 붙여 의존성을 주입받을 수 있다.
  • 현재 가장 권장되는 의존 관계 주입 방식
  • 생성자 호출시점에 딱 1번만 호출되는 것이 보장
  • 불변, 필수 의존관계에 사용
  • 생성자 주입시 필드에 final 키워드를 사용할 수 있다.
    • final 키워드를 사용하기에 생성자로 인해 인스턴스가 생성될 때 1번만 할당된다.
    • final 키워드를 사용해서 값이 한 번 할당되면 변경할 수 없기에 객체의 불변성 보장
  • 생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입된다.
  • 초기에 할당되기에 NPE(NUll Pointer Exception)이 절대 발생 X
  • @RequiredArgsConstructor은 롬복의 어노테이션 중 하나로, final 키워드가 붙은 주입에만 생성자를 만들어준다. 만약 final 키워드를 사용하지 않으면 @AllArgsConstructor 어노테이션을 사용하면 된다.
@Component
public class OrderServiceImpl implements OrderService {
	private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;
    
    @Autowired
    public orderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
    	this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
    
}

 

장점

  • 의존 관계를 모두 주입해야만 객체 생성이 가능하므로 NPE를 방지할 수 있다.
  • 불변성을 보장할 수 있다.
  • 순환 참조를 컴파일 단계에서 찾아낼 수 있다.

→ DI는 생성자 주입, 수정자 주입, 필드 주입이 있다.

생성자 주입은 생성자 호출시점에 딱 1번만 호출되는 것을 보장하며 불변, 필수 의존관계에 사용한다.

수정자 주입은 선택, 변경 가능성이 있는 의존관계에 사용되며 빈을 선택적으로 주입이 가능하다.

필드 주입은 외부에서 변경이 불가능하며 테스트하기 힘들다. DI 프레임워크 없이는 작동하기 힘들며, 주로 애플리케이션의 실제 코드와 관계 없는 테스트 코드나 @Configuration 같은 스프링 설정 목적으로 사용한다.

 

순환 참조

순환 참조란 서로 다른 여러 빈들이 서로를 참조하고 있음을 의미한다.

 

 

순환 참조는 언제 발생하는가 ?

필드 주입이나 수정자 주입은 객체 생성 후 비즈니스 로직 상에서 순환 참조가 일어나기 때문에 컴파일 단계에서 순환 참조를 잡아낼 수 없다. 반면에 생성자 주입을 사용하면 스프링 컨테이너가 빈을 생성하는 시점에 순환 참조를 확인하기 때문에 컴파일 단계에서 순환 참조를 잡아낼 수 있다.

 

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

  • 의존 관계를 모두 주입하지 않은 경우에 객체를 생성할 수 없기 때문에 NPE가 발생하지 않는다.
  • final 키워드를 사용할 수 있어 불변성을 보장할 수 있다.
  • 생성자 주입은 컴파일 단계에서 순환 참조를 잡아낼 수 있다.
  • 의존성을 주입하기 번거롭고 생성자 인자가 많아지면 코드가 길어져 위기감을 느낄 수 있다. 이를 바탕으로 SRP 원칙을 생각하게 되고, 리팩토링을 하게 된다.
  • DI 컨테이너 없이 직접 의존성을 주입할 수 있다.

 

Autowired

  • DI를 할 때 사용하는 어노테이션이며, 의존 관계의 타입에 해당하는 빈을 찾아 주입하는 역할을 한다.
  • 쉽게 말하자면, 스프링 서버가 올라갈 때 애플리케이션 컨텍스트가 @Bean이나 @Service, @Controller 등 어노테이션을 이용하여 등록한 스프링 빈을 생성하고, @Autowired 어노테이션이 붙은 위치에 의존 관계 주입을 수행하게 된다.

 

Autowiring 동작 과정

스프링 서버가 올라갈 때 애플리케이션 컨텍스트가 @Bean이나 @Service, @Controller 등 어노테이션을 이용하여 등록한 스프링 빈을 생성하고, @Autowired 어노테이션이 붙은 위치 또는 생성자, 수정자를 통해 주입한다.

 

 

배경

IoC라는 용어가 매우 느슨하게 정의되어 있기 때문에 스프링을 IoC 컨테이너라고만 표현해서는 스프링이 제공하는 기능의 특징을 명확하게 설명하지 못한다. 스프링이 서블릿 컨테이너처럼 서버에서 동작하는 서비스 컨테이너라는 뜻인지, 아니면 단순히 IoC 개념이 적용된 템플릿 메서드 패턴을 이용해 만들어진 프레임워크인지, 아니면 또 다른 IoC 특징을 지닌 기술이라는 것인지 파악하기 힘들다. 그래서 스프링이 제공하는 IoC 방식의 핵심을 짚어주는 용어인 의존관계 주입(Dependency Injection) 이라는 용어가 탄생했다.

스프링 IoC 기능의 대표적인 동작원리는 주로 의존관계 주입이라고 불린다. 스프링이 컨테이너고 프레임워크이기 때문에 기본적인 동작원리가 모두 IoC라고 할 수 있지만 스프링이 여타 프레임워크와 차별화돼서 제공해주는 기능은 의존관계 주입이라는 용어를 사용할 때 더 분명하게 드러난다. 그래서 초기에는 주로 IoC 컨테이너라고 불리던 스프링이 지금은 의존관계 주입 컨테이너 혹은 DI 컨테이너라고 더 많이 불리고 있다.

 

의존성 주입이 필요한 이유

  1. 두 클래스가 강하게 결합되어 있음.
  • 두 클래스가 강하게 결합되어 있기 때문에 만약 다른 클래스로 변경하고자 한다면 클래스의 생성자에 변경이 필요하다. 즉, 유연성이 떨어진다. 이에 대한 해결책으로 상속을 떠올릴 수 있지만, 상속은 제약이 많고 확장성이 떨어지므로 피하는 것이 좋다.

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

  • 올바른 객체지향적 설계라면 객체들 간에 관계가 맺어져야 한다. 객체들 간에 관계가 맺어졌다면 다른 객체의 구체 클래스를 전혀 알지 못하더라도, (해당 클래스가 인터페이스를 구현했다면) 인터페이스의 타입으로 사용할 수 있다.

 

→ 결국 위와 같은 문제점이 발생하는 근본적인 이유는 관심사가 분리되지 않았기 때문이다. 스프링에서는 DI를 적용하여 이러한 문제를 해결하고자 하였다.

 

Spring IoC / DI의 동작 과정

  • IoC(제어의 역전)은 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것으로 코드의 최종 호출은 개발자가 제어하는 것이 아닌 프레임워크의 내부에서 결정한대로 이루어진다.
  • DI(의존관계 주입)은 스프링 프레임워크에서 지원하는 IoC의 형태로 객체(빈) 사이의 의존관계를 빈 설정 정보를 바탕으로 DI 컨테이너가 자동으로 연결한다.

 

DI와 IoC의 차이

  • DI는 의존관계를 어떻게 가질 것인가에 대한 문제이고, IoC는 누가 소프트웨어의 제어권을 갖고 있느냐의 문제이다. IoC 컨테이너가 빈을 생성할 때 빈들간의 의존관계를 DI를 통해 해결한다. DI는 IoC 사용을 필수롤 요구하지 않는다는 점을 주의해야 한다

 

정리

한 객체가 어떤 객체(구체 클래스)에 의존할 것인지는 별도의 관심사이다. 스프링은 의존성 주입을 도와주는 DI 컨테이너로서, 강하게 결합된 클래스들을 분리하고, 애플리케이션 실행 시점에 객체 간의 관계를 결정해줌으로써 결합도를 낮추고 유연성을 확보해준다. 이러한 방법은 상속보다 훨씬 유연하다. 단, 한 객체가 주입 받으려면 반드시 DI 컨테이너에 의해 관리되어야 한다는 것이다.

 

 

[Spring] Spring IoC와 DI란?

 

[Spring] Spring IoC와 DI란?

spring-study에서 스터디를 진행하고 있습니다. IoC란? IoC란 Inversion of Control의 줄임말이며, 제어의 역전이라고 한다. 스프링 애플리케이션에서는 오브젝트(빈)의 생성과 의존 관계 설정, 사용, 제거

steady-coding.tistory.com

 

[Spring] 의존성 주입(Dependency Injection, DI)이란? 및 Spring이 의존성 주입을 지원하는 이유

 

[Spring] 의존성 주입(Dependency Injection, DI)이란? 및 Spring이 의존성 주입을 지원하는 이유

1. 의존성 주입(Dependency Injection)의 개념과 필요성 [ 의존성 주입(Dependency Injection) 이란? ] Spring 프레임워크는 3가지 핵심 프로그래밍 모델을 지원하고 있는데, 그 중 하나가 의존성 주입(Dependency Inj

mangkyu.tistory.com

 

728x90
LIST

'Backend > Spring Boot' 카테고리의 다른 글

Spring IoC  (0) 2024.06.30
인텔리제이 단축키 모음  (0) 2024.01.06