
이전 글에서 Spring이 DispatcherServlet으로 반복되고 귀찮은 일을 처리해주기 때문에 Controller만 잘 만들면 된다고 했다.
하지만 Controller클래스 하나로 비즈니스로직과 DB연결된 작업 모두 처리하면 기능이 추가될수록 코드가 복잡해지고 유지보수가 어려워진다.
이러한 문제를 해결하기 위해 Controller, Service, Repository 3개로 분리하여 3 Layer Architecture를 도입할 수 있다.
위와 같은 구조로 연결된다고 볼 수 있다.
IoC는 말 그대로 "제어권이 뒤바뀌었다"는 뜻이다.
프레임워크가 없을 때 (일반적인 제어 흐름)
프레임워크를 사용하지 않는 자바 프로그램에서는 개발자가 직접 객체를 생성하고, 실행하고, 소멸시키는 등 객체의 생명주기를 관리합니다. 외부 라이브러리를 가져다 쓰더라도, 언제 호출할지는 개발자가 직접 결정한다. 즉, 개발자가 코드의 주인이다.
프레임워크를 사용할 때 (제어의 역전)
Spring을 사용하면 상황이 달라진다. 우리는 Controller, Service, Repository 같은 비즈니스 로직만 구현해 놓을 뿐, 이 객체들이 정확히 언제 생성되고 호출되는지는 신경 쓰지 않는다. 대신 Spring 컨테이너가 적절한 시점에 객체를 생성하고 로직을 실행한다.
이처럼 객체의 생명주기 관리와 흐름 제어권이 개발자에서 프레임워크로 넘어간 것을 제어의 역전(IoC)이라고 부른다.
IoC의 장점
DI는 IoC라는 거대한 원칙을 실현하기 위한 디자인 패턴 중 하나이다. 많은 분이 IoC와 DI를 동일하게 생각하지만, 엄밀히 말하면 DI는 IoC를 달성하기 위한 구체적인 방법론이다.
즉, 객체가 스스로 필요한 객체를 만드는 것이 아니라, 외부(Spring)에서 만들어진 객체를 넣어주는 것이 DI의 핵심이다.
Spring에서 의존성을 주입하는 방식은 크게 세 가지가 있다.
생성자 주입
클래스의 생성자를 통해 의존성을 주입받는 방식입니다. Spring에서 가장 권장하는 방식이다.
public class A {
private final B b; // final 키워드 사용 가능
// 생성자를 통해 B 객체를 주입받음
public A(B b) {
this.b = b;
}
}
장점: 객체 생성 시점에 의존성이 주입되므로 불변성을 보장하며, 테스트 코드 작성 시에도 용이하다.
메서드(세터) 주입
Setter 메서드를 통해 의존성을 주입받는 방식이다.
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
특징: 주입받는 객체가 변경될 가능성이 있는 경우 사용하지만, 실무에서는 드물다.
필드 주입
변수(필드)에 바로 @Autowired를 붙여 주입받는 방식입니다.
public class A {
@Autowired
private B b;
}
특징: 코드가 매우 간결해 보이지만, 외부에서 변경이 불가능하고 테스트하기 어렵다는 단점이 있어 권장되지 않는다.
위에서 제어의 역전(IoC)과 의존성 주입(DI)의 개념을 알아봤는데 그렇다면 스프링에서는 대체 '어디서' 이 객체들을 관리하고, '무엇'을 관리하는 걸까? 그 정답인 IoC 컨테이너와 Bean에 대해 핵심만 콕콕 집어 정리해 보자.
스프링 컨테이너는 자바 객체의 생명주기를 관리하며, 생성된 객체들에게 추가적인 기능을 제공하는 공간이다.
Bean은 스프링 컨테이너에 의해 관리되는 자바 객체를 의미한다. 우리가 new 키워드로 직접 생성한 객체가 아니라, 스프링이 대신 만들어 컨테이너에 담아둔 객체들이다.
스프링에게 "이 객체는 네가 관리해줘!"라고 알리는 방법은 크게 두 가지이다.