[Spring] Spring IOC, DI

Junwon SEO·2022년 11월 10일
0

Spring 스터디

목록 보기
3/5

스프링을 공부하다 보면 꼭 등장하는 개념이 IoC / DI 이다. 앞서 포스팅한 스프링 프레임워크의 특징에도 설명했듯이 흔히 이야기하는 스프링 프레임워크의 3 요소는 IoC / DI / AOP 이다. 이번에는 이 중 DI와 IoC에 대해서 설명하였다.

1. IoC (Inversion Of Control)

흔히들 제어의 역전 정도로 표현한다.

제어가 역전된다. 너무 투박한 표현이라 이해가 잘 되지 않는다. IoC를 설명할 때 할리우드 원칙을 예시로 들면 이해가 조금 수월하다. "Don't call us. We'll call you." 이는 제어의 역전에 대한 비유적 표현으로, 본래 오디션에 떨어진 배우들에게 영화사에서 하던 말이 프로그래밍 용어로 변형되어 사용되고 있다고 한다. 말 그대로 배우들(객체)에게 영화사에서 필요하면 연락할테니 먼저 연락(호출/생성)하지 말라는 뜻이 된다.

좀 더 자세히 이해해보기 위해 프레임워크를 적용하지 않은, 우리가 그동안 작성해왔던 일반적인 프로그램을 생각해보자. 객체의 생명주기(객체의 생성, 초기화, 소멸, 메서드 호출 등)를 클라이언트 구현 객체가 직접 관리한다. 또한 다른 사람이 작성한 외부 코드(라이브러리)를 호출하더라도 해당 코드의 호출 시점 역시 직접 관리한다. 기존에는 이와 같이 모든 객체 또는 메서드의 호출을 프로그램을 작성하는 프로그래머가 결정하고 관리했다.

스프링과 같은 프레임워크를 사용할 때를 생각해보자. Controller, Service 같은 객체들의 동작을 우리가 직접 구현하기는 하지만, 해당 객체들이 어느 시점에 호출될 지는 신경쓰지 않는다. 단지 프레임워크가 요구하는대로 객체를 생성하면, 프레임워크가 해당 객체들을 가져다가 생성하고, 메서드를 호출하고, 소멸시킨다. 프로그램의 제어권이 프로그래머에서 시스템으로, 말 그대로 제어가 역전된 것이다.

IoC 사용 시 얻을 수 있는 장점

  • 프로그램의 진행 흐름과 구체적인 구현을 분리시킬 수 있다.
  • 개발자는 비즈니스 로직에 집중할 수 있다.
  • 구현체 사이의 변경이 용이하다.
  • 객체 간 의존성이 낮아진다.

2. 의존성이란 무엇일까?

의존대상 B가 변하면, 그것이 A에 영향을 미친다.

public class A {
    private B b = new B();
}

위와 같은 예시를 보자. A클래스는 B라는 클래스를 필드로 가진다. 다시 말해 A라는 객체를 생성하기 위해서는 반드시 B라는 객체를 생성해야 한다. 그런데 만약 B에 final 필드가 추가되는 변경이 일어난다면 어떨까? A 클래스 내부의 new B() 부분에서 컴파일 에러가 나게 될 것이다. B 클래스의 내부에 변경이 일어났는데, A 클래스에도 영향을 미치게 되는 것이다. 이런 경우를 "A가 B에 의존한다." 라고 한다.

3. DI (Dependency Injection)

의존성 주입

흔히 IoC와 DI를 동일시하거나 헷갈려 하곤 하는데 둘은 사뭇 다른 개념이다. 넓은 의미에서 IoC는 DI를 포함하고 있으며 DI는 IoC를 구현하기 위한 디자인 패턴의 일종으로, 이름 그대로 객체의 의존관계를 외부에서 주입시키는 패턴을 말한다.

public class A {
    private B b = new B();
}

그렇다면 앞서 다뤘던 위와 코드는 A 클래스 내부에서 B 객체를 생성하고 있기 때문에 A가 반드시 내부에서 생성한 B 인스턴스에 의존하는 것으로 의존관계가 고정된다. 그러면 이러한 의존 관계는 유지하되 의존 대상을 객체 내부에서 직접 생성(결정)하는 것이 아니라 외부로부터 주입받을 수는 없을까?

결론부터 이야기하자면 스프링에서 의존성을 외부에서부터 주입하는 방법에는 setter 주입, field 주입, constructor(생성자) 주입 크게 3가지가 있다.

1. 세터 주입(Setter Injection)

public class A {
    private B b;
    
    @Autowired
    public void setB(final B b){
    	this.b = b;
    }
}
  • @Autowired 사용
  • Final 선언 불가
  • setter를 통해 값이 언제든지 변경 될 수 있기 때문에 SOLID 원칙 중 OCP(개방폐쇄원칙)를 위반한다.

2. 필드 주입(Field Injection)

public class A {
    @Autowired
   	private B b;
}
  • @Autowired 사용
  • 사용이 너무 편함
  • SOLID 원칙 중 SRP를 위반한다.
    결과적으로 너무 많은 의존성을 갖게 되며 의존성이 눈에 보이지 않는다. 따라서 bean 구현을 하나 하나 전부 뜯어봐야 하며 테스트를 할 때 new로 생성을 해줘야된다.
  • Final 선언 불가 -> 새로 할당 하는 시점이 있을 수 있다.
  • DI(스프링) 컨테이너와 강한 결합

* 주의사항
스프링의 DI 컨테이너는 의존하는 bean 간에 느슨한 결합을 제공해준다. 하지만 @Autowired 어노테이션을 이용한 필드 인젝션을 하면 스프링을 통해서만 의존성 주입이 가능하기 때문에 해당 Bean들이 스프링의 DI 컨테이너와의 강한 결합을 하게 된다. 모순적으로 Spring DI container를 사용하는 이유와 정 반대인 상황이 발생하기 때문에 @Autowired의 사용은 지양해야한다.

3. 생성자 주입(Constructor Injection) (권장)

public class A {
    private B b;
    public A(B b) {
        this.b = b;
    }
}
  • @Autowired 생략 가능
  • Final 사용 가능하여 불변성을 보장할 수 있다.
  • 테스트가 용이하다.
  • Lombok이라는 라이브러리와 결합이 좋다.
  • 순환 참조를 막을 수 있다.

Spring DI 사용 시 얻을 수 있는 이점

  • 의존성이 줄어든다. (변경에 덜 취약해진다.)
  • 모의 객체를 주입할 수 있기 때문에 단위 테스트가 쉬워진다.
  • 가독성이 높아진다.
  • 재사용성이 높아진다.

0개의 댓글