IoC(제어의 역전)와 DI(의존성 주입)는 스프링 프레임워크의 근간이자, 객체 지향 설계를 유연하게 만드는 핵심 원리이다.
일반적인 프로그래밍 흐름에서는 개발자(나) 가 작성한 코드가 프로그램의 흐름을 주도한다.
내가 객체를 생성하고, 내가 메서드를 호출하고 내가 의존성을 연결한다.
하지만 IoC가 적용되면, 이 제어권이 프레임워크(스프링)로 넘어간다. 내가 코드를 짜서 프레임워크게 던져놓으면, 프레임워크가 내 코드를 대신 호출하고 관리해준다.
IoC가 "제어권을 넘긴다"는 추상적인 개념이라면, DI는 "그래서 제어권을 넘겨서 객체를 어떻게 관리할 건데?"에 대한 구체적 방법이다.
객체 A가 객체 B를 사용해야 할 떄(의존할 떄), 객체 A가 직접 B를 생성하는 것기 아니라, 외부(스프링 컨테이너)에서 생성된 B를 A에게 넣어주는 방식이다.
코드 비교 : DI가 없을 때 vs 있을 때
1) DI 미적용 (강한 결합)
public class Car {
private GasEngine engine;
public Car() {
// Car가 Engine을 직접 생성 (제어권이 Car에게 있음)
this.engine = new GasEngine();
}
}
2) DI 적용 (느슨한 결합)
public class Car {
private Engine engine; // 구체적인 GasEngine이 아닌 인터페이스에 의존
// 생성자를 통해 외부에서 Engine을 받아옴 (주입받음)
public Car(Engine engine) {
this.engine = engine;
}
}
왜 이렇게 복잡하게 쓸까? (장점)
IoC와 DI를 사용하면 코드가 레고 블록처럼 변한다.
1. 유연성 (Loose Coupling):
위의 Car예제에서 ElectricEngine을 넣든 HydrogenEngine을 넣든 Car 코드는 수정할 필요가 없다. 부품만 갈아 끼우면 된다.
2. 테스트 용이성 :
진짜 데이터베이스 대신 "가짜(Mock)데이터베이스" 객체를 주입해서 테스트하기가 훨씬 쉽다.
3. 코드 간결성:
객체의 생성과 소멸 같은 복잡한 관리를 프레임워크가 대신 해주므로, 개발자는 비즈니스 로직에만 집중할 수 있따.
스프링에서의 활용(3가지 DI 방법)
스프링 프레임워크는 IoC 컨테이너(ApplicationCOntext) 라는 존재가 이 DI 작업을 전담한다.
1 . 생성자 주입 (Constructor Injection) :
2. 세터 주입 : setEngine() 같은 메서드로 주입 받기.
3. 필드 주입 : @Autowired 를 변수 위에 붙이는 방식
둘 다 스프링 컨테이너지만, 제공하는 기능의 범위가 다르다.
ApplcationContext는 BeacnFactory 인터페이스를 상속 받는다.
즉, ApplicationContext는 BeanFactory의 일종이므로 "빈 팩토리이다"라고 말할 수 있지만, 기능적으로 훨씬 거대하다.

그렇다면 왜 즉시 로딩이 더 좋을까?
StaticApplicationContext는 코드를 통해 빈 메타정보를 등록하기 위해 사용한다.
스프링의 기능에 대한 학습 테스트를 만들 때를 제외하면 실제로 사용되지 않음.
그럼에도 이 클래스를 사용하는 이유는, 스프링 IoC 컨테이너는 파일 포맷이나 리소스 종류에 독립적이며 오브젝트로 표현되는 순수한 메타정보를 사용한다는 걸 보여주기 위해서.
GenericApplicationContext 는 가장 일반적인 애플리케이션 컨텍스트의 구현 클래스이다.
실전에서 사용될 수 있는 모든 기능을 갖추고 있는 애플리케이션 컨텍스트다.
GenericApplicationContext는 StaticApplicationContext와는 달리 XML 파일과 같은 외부의 리소스에 있는 빈 설정 메타정보를 리더를 통해 읽어들여서 메타정보로 전환해서 사용한다.
스프링에서는 대표적으로 XML 파일, 자바 소스코드 어노테이션, 자바 클래스 세 가지 방식으로 빈 설정 메타정보를 작성할 수 있다. 물론 이 세가지 방식으로 제한되지는 않는다.
GenericApplicationContext를 직접 이용할 일이 있을까? 아마 대부분의 스프링 사용 개발자라면 없을 것임.
스프링 사용하는 독립형 애플리에키션 만들지 않는 한 직접 이용할 필요 X
JUnit 테스트는 테스트 내에서 사용할 수 있도록 애플리케이션 컨텍스트를 자동으로 만들어준다.
이때 생성되는 애플리케이션 컨텍스트가 바로 GenericApplicationContext다.
스프링 애플리케이션에서 가장 많이 사용되는 애플리케이션 WebApplicationContext이다.
추가된 빈 스코프(Scope): 일반적인 singleton, prototype 외에도 웹 요청 주기에 맞춘 특수 스코프를 지원합니다.
request: HTTP 요청마다 별도의 빈 생성
session: HTTP 세션마다 별도의 빈 생성
application: ServletContext 생명주기에 맞춰 빈 생성
계층 구조: 보통 하나가 아니라, 역할에 따라 Root와 Servlet 두 가지 계층으로 나뉘어 관리됩니다.
① Root WebApplicationContext
역할: 웹 애플리케이션 전체에서 공유되는 공통 서비스를 관리합니다.
대상: Service, Repository(DAO), DB 설정 등 비즈니스 로직과 데이터 계층.
특징: 여러 개의 DispatcherServlet이 있더라도 모두가 공유할 수 있는 부모 컨텍스트입니다.
② Servlet WebApplicationContext
역할: 특정 DispatcherServlet에 국한된 웹 관련 설정을 관리합니다.
대상: Controller, ViewResolver, HandlerMapping 등 MVC 관련 빈.
특징: Root 컨텍스트를 부모로 둡니다. 즉, Controller(자식)는 Service(부모)를 참조할 수 있지만, 반대로 Service는 Controller를 참조할 수 없습니다.
ContextLoaderListener가 실행되며 Root WebApplicationContext를 생성합니다.
클라이언트의 요청이 들어오면 DispatcherServlet이 초기화되면서 자신만의 Servlet WebApplicationContext를 생성하고 Root를 부모로 등록합니다.
데이터 처리나 비즈니스 로직(Root)은 웹 기술(Spring MVC, Struts 등)이 바뀌더라도 그대로 유지될 수 있도록 독립시키고, 웹 프레젠테이션 계층(Servlet)은 유연하게 교체하거나 여러 개를 둘 수 있도록 설계한 것입니다.
참고: 최근 Spring Boot에서는 이러한 복잡한 계층 설정을 내부적으로 자동화해주기 때문에, 개발자가 직접 web.xml을 만지며 계층을 나눌 일은 줄어들었지만, 내부 원리는 여전히 동일하게 작동합니다.