[Spring] IoC와 DI - 복습편

clean·2024년 3월 9일

Spring 입문 강의

목록 보기
5/6
post-thumbnail

그동안 스프링의 핵심 기능들에 대해 공부한 내용들을 쭉 복습하며 정리한 글입니다.

IoC와 DI에 대하여

Dependency Injection

DI(Dependency Injection)은 말 그대로 의존성 주입이다.
의존성(Dependency)이란, A 클래스가 B 클래스를 참조하고 있다면(즉 A 클래스 내부에서 B 클래스를 사용, 접근하고 있다면) A 클래스는 B 클래스에 의존하고 있다고 한다.

이런 의존성을 A 클래스 내부에서 직접 정할 수도 있지만(아래 예시처럼 new 연산자를 이용해서)

// A 클래스의 내부입니다.
// 클래스B는 '인터페이스'라는 이름의 인터페이스를 구현한 클래스입니다.
인터페이스 example = new 클래스B();

A 클래스가 의존할 구체 클래스를 직접 선택하지 않고 밖에서 의존성을 넣어줄 수도 있다. 이것이 DI이다.

private 인터페이스 example;

// 생성자를 통한 의존성 주입.
// A 클래스는 example에 클래스B가 들어올지, 클래스 C가 들어올지 알 수 없다.
클래스A(인터페이스 example) {
	this.example = example;
}

DI를 해주는 방법에는

  • 프로그래머가 다른 클래스에서 클래스A 인스턴스를 만들 때 의존성을 넣어주는 코드를 작성
  • 스프링프레임워크가 IoC 컨테이너를 통해 DI 해줌

크게 이런 방법들이 있다.

Inversion of Control

그렇다면 IoC란 뭘까.
스프링 프레임워크가 제공하는 DI 기능을 IoC라고 한다.

즉, DI를 구현하는 방법들 중 하나가 스프링을 이용한 IoC인 것이다.


IoC와 자동 의존 관계 주입

스프링 프레임워크가 제공하는 IoC는

  • Configuration 파일을 통해 수동으로 의존성을 주입하는 방법
  • 컴포넌트 스캔과 @Autowired를 통해서 의존성을 자동 주입하는 방법

등이 있는데, 요즘 주로 쓰이는 것은 자동 주입이다.

의존관계 자동 주입에는 크게 아래 세가지 방법이 있다.

  • 필드 주입
  • 메소드 주입(setter 주입이 여기에 포함)
  • 생성자 주입

필드 주입(Field Injection)

@Autowired
필드타입 필드명;

이렇게 필드 위에 바로 @Autowired를 붙여서 의존성을 주입하는 것이다.

이 방법의 장점은

  • 코드가 쉽고 간결
  • 한번 의존성이 주입되면 변경될 가능성 없음(안전)

이 방법의 단점은

  • 외부에서 변경이 불가능하여 테스트하기 어렵다.
  • 스프링에 의존적이다 (IoC 컨테이너가 없으면 아무것도 할 수가 없다.)
  • 순환참조에 대해 방어할 수가 없다.

뒤에서 한번 더 언급하겠지만, 필드 주입은 🚨안티 패턴🚨이니 쓰지 말자...
(애플리케이션과 관련없는 테스트코드에서는 쓰기도 한다. -> 그렇다면 @SpringBootTest 달고 테스트해야 되고 순수 자바로 단위 테스트는 못하겠지..?)

메소드 주입

메소드 위에 @Autowired 어노테이션을 붙여서 의존성을 주입받을 수 있다.

@Autowired
public void init(MemberRepository memberRepository) {
	this.memberRepository = memberRepository;
}

setter 주입이라는 주입 방법도 있는데, 그냥 setter 위에다 @Autowired 붙인 것이므로 메소드 주입과 똑같다.

이 방법의 장점은

  • 한번 DI를 하고도 나중에 의존성을 바꿀 수 있다. -> 하지만 보통은 처음 의존성을 쭉 유지하지 도중에 바꿔야 하도록 설계하는 경우는 거의 없으므로 메소드 주입은 거의 사용하지 않는다.
  • Lazy Init 가능: 객체가 생성되는 시점과 DI 되는 시점을 분리할 수 있다. -> 장점일까..? 객체가 안쓰일 때 생성 안해서 메모리를 아껴주니까 장점인가,,?
  • 테스트 코드 짤 때 메소드를 통해서 Mock 객체 넣을 수 있음

이 방법의 단점은

  • 런타임시에 의존성이 바뀔 가능성이 있다. (안전 X. 누가 런타임에 의존성 막 바꾸는 코드 넣으면 대환장 파티)
  • 의존성 주입 없이 객체 생성이 가능해서 NullPointerException 가능
  • 순환참조에 대해 방어할 수가 없다.

순환 참조A 클래스와 B 클래스가 서로를 참조하고 있는 상황을 의미한다.

아래에서도 말하겠지만, 생성자 주입은 순환참조가 발생했을 때 앱 구동을 할 수 없도록 친절하게 Exception을 던져준다고 한다.

하지만 메소드 주입과 필드 주입은 Exception을 던져주지 않는다.

생성자 주입

생성자 주입은 생성자 위에 @Autowired 어노테이션을 달아서 자동 주입하는 방법이다.

@Autowired
public OrderServiceImpl implements OrderService {
	private final MemberRepository memberRepository;
    
    public OrderServiceImpl(MemberRepository memberRepository) {
    	this.memberRepository = memberRepository;
    }
}

이 방법의 장점은

  • 객체를 생성할 때 무조건 의존성을 넣어주어야 하므로 NullPointerException을 막을 수 있다.
    - 필드에 final 키워드를 사용할 수 있다. -> 런타임 중에 의존성 바뀌지 X. 안전
  • 순환 참조가 발생하면 프레임워크가 친절하게 알려준다.

보통 의존성을 주입받을 필드에 final 을 붙여놓고 롬복 라이브러리의 @RequiredArgsConstructor와 함께 사용하면 가장 좋은 듯 하다.

그리고 스프링 프레임워크를 쓸 때, 클래스 내에 생성자가 딱 한개이면 @Autowired를 붙이지 않아도 자동으로 생성자 주입을 해준다는 특징이 있다.

그래서 뭘 쓰나요?

-> 무적권 생성자 주입을 쓰세요.


Reference

profile
블로그 이전하려고 합니다! 👉 https://onfonf.tistory.com 🍀

0개의 댓글