Spring의 여러 개념 (IoC/Di, BeanFactory, ApplicationContext... 등)

토토들·2026년 1월 19일

IoC(제어의 역전)와 DI(의존성 주입)는 스프링 프레임워크의 근간이자, 객체 지향 설계를 유연하게 만드는 핵심 원리이다.

1. IoC(Inversion of Control, 제어의 역전)

개념

일반적인 프로그래밍 흐름에서는 개발자(나) 가 작성한 코드가 프로그램의 흐름을 주도한다.
내가 객체를 생성하고, 내가 메서드를 호출하고 내가 의존성을 연결한다.
하지만 IoC가 적용되면, 이 제어권이 프레임워크(스프링)로 넘어간다. 내가 코드를 짜서 프레임워크게 던져놓으면, 프레임워크가 내 코드를 대신 호출하고 관리해준다.

2. DI (Dependency Injection, 의존성 주입)

개념

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;
    }
}
  1. 왜 이렇게 복잡하게 쓸까? (장점)
    IoC와 DI를 사용하면 코드가 레고 블록처럼 변한다.

    1. 유연성 (Loose Coupling):
    	위의 Car예제에서 ElectricEngine을 넣든 HydrogenEngine을 넣든 Car 코드는 수정할 필요가 없다. 부품만 갈아 끼우면 된다. 
     2. 테스트 용이성 : 
     	진짜 데이터베이스 대신 "가짜(Mock)데이터베이스" 객체를 주입해서 테스트하기가 훨씬 쉽다. 
     3. 코드 간결성: 
     	객체의 생성과 소멸 같은 복잡한 관리를 프레임워크가 대신 해주므로, 개발자는 비즈니스 로직에만 집중할 수 있따. 
        
  2. 스프링에서의 활용(3가지 DI 방법)
    스프링 프레임워크는 IoC 컨테이너(ApplicationCOntext) 라는 존재가 이 DI 작업을 전담한다.
    1 . 생성자 주입 (Constructor Injection) :
    2. 세터 주입 : setEngine() 같은 메서드로 주입 받기.
    3. 필드 주입 : @Autowired 를 변수 위에 붙이는 방식

빈 팩토리(BeanFactory)와 애플리케이션 컨텍스트(ApplicationContext)는 다른가?

둘 다 스프링 컨테이너지만, 제공하는 기능의 범위가 다르다.
ApplcationContext는 BeacnFactory 인터페이스를 상속 받는다.

  • BeanFactory: 스프링 컨테이너의 최상위 인터페이스(뿌리)
  • ApplicationContext: BeanFactory의 기능을 모두 물려받고, 수많은 부가 기능을 더한 인터페이스

즉, ApplicationContext는 BeanFactory의 일종이므로 "빈 팩토리이다"라고 말할 수 있지만, 기능적으로 훨씬 거대하다.

  1. 핵심 차이점 비교.
    가장 큰 차이 부가 기능의 유무빈을 생성하는 시점

그렇다면 왜 즉시 로딩이 더 좋을까?

  • ApplicationContext는 애플리케이션을 시작할 때 모든 빈(싱글톤)을 미리 만들어 둔다. 만약 설정 정보에 오타가 있거나 빈 의존 관계가 꼬여있다면, 서버를 띄우는 시점에 바로 에러가 터져서 개발자가 즉시 알 수 있다.

StaticApplicationContext

StaticApplicationContext는 코드를 통해 빈 메타정보를 등록하기 위해 사용한다.
스프링의 기능에 대한 학습 테스트를 만들 때를 제외하면 실제로 사용되지 않음.
그럼에도 이 클래스를 사용하는 이유는, 스프링 IoC 컨테이너는 파일 포맷이나 리소스 종류에 독립적이며 오브젝트로 표현되는 순수한 메타정보를 사용한다는 걸 보여주기 위해서.

GenericApplicationContext

GenericApplicationContext 는 가장 일반적인 애플리케이션 컨텍스트의 구현 클래스이다.
실전에서 사용될 수 있는 모든 기능을 갖추고 있는 애플리케이션 컨텍스트다.
GenericApplicationContext는 StaticApplicationContext와는 달리 XML 파일과 같은 외부의 리소스에 있는 빈 설정 메타정보를 리더를 통해 읽어들여서 메타정보로 전환해서 사용한다.

스프링에서는 대표적으로 XML 파일, 자바 소스코드 어노테이션, 자바 클래스 세 가지 방식으로 빈 설정 메타정보를 작성할 수 있다. 물론 이 세가지 방식으로 제한되지는 않는다.

GenericApplicationContext를 직접 이용할 일이 있을까? 아마 대부분의 스프링 사용 개발자라면 없을 것임.
스프링 사용하는 독립형 애플리에키션 만들지 않는 한 직접 이용할 필요 X
JUnit 테스트는 테스트 내에서 사용할 수 있도록 애플리케이션 컨텍스트를 자동으로 만들어준다.
이때 생성되는 애플리케이션 컨텍스트가 바로 GenericApplicationContext다.

WebApplicationContext

스프링 애플리케이션에서 가장 많이 사용되는 애플리케이션 WebApplicationContext이다.

  1. 주요 특징
    일반적인 ApplicationContext와 비교했을 때, WebApplicationContext만이 가지는 차별점은 다음과 같다.

    ServletContext와의 연결: 웹 애플리케이션 전체의 정보를 담고 있는 ServletContext와 연결되어 있습니다. 이를 통해 서블릿 환경에 접근할 수 있습니다.

추가된 빈 스코프(Scope): 일반적인 singleton, prototype 외에도 웹 요청 주기에 맞춘 특수 스코프를 지원합니다.

request: HTTP 요청마다 별도의 빈 생성

session: HTTP 세션마다 별도의 빈 생성

application: ServletContext 생명주기에 맞춰 빈 생성

계층 구조: 보통 하나가 아니라, 역할에 따라 Root와 Servlet 두 가지 계층으로 나뉘어 관리됩니다.

  1. 계층 구조 (Hierarchy)
    Spring MVC 구조에서 WebApplicationContext는 보통 두 가지 수준으로 존재하며 상속 관계를 형성합니다.

① Root WebApplicationContext
역할: 웹 애플리케이션 전체에서 공유되는 공통 서비스를 관리합니다.

대상: Service, Repository(DAO), DB 설정 등 비즈니스 로직과 데이터 계층.

특징: 여러 개의 DispatcherServlet이 있더라도 모두가 공유할 수 있는 부모 컨텍스트입니다.

② Servlet WebApplicationContext
역할: 특정 DispatcherServlet에 국한된 웹 관련 설정을 관리합니다.

대상: Controller, ViewResolver, HandlerMapping 등 MVC 관련 빈.

특징: Root 컨텍스트를 부모로 둡니다. 즉, Controller(자식)는 Service(부모)를 참조할 수 있지만, 반대로 Service는 Controller를 참조할 수 없습니다.

  1. 동작 방식 요약
    서블릿 컨테이너(Tomcat 등)가 구동되면서 web.xml이나 자바 설정을 읽습니다.

ContextLoaderListener가 실행되며 Root WebApplicationContext를 생성합니다.

클라이언트의 요청이 들어오면 DispatcherServlet이 초기화되면서 자신만의 Servlet WebApplicationContext를 생성하고 Root를 부모로 등록합니다.

  1. 왜 이렇게 나누나요?
    이렇게 계층을 나누는 이유는 관심사의 분리(Separation of Concerns) 때문입니다.

데이터 처리나 비즈니스 로직(Root)은 웹 기술(Spring MVC, Struts 등)이 바뀌더라도 그대로 유지될 수 있도록 독립시키고, 웹 프레젠테이션 계층(Servlet)은 유연하게 교체하거나 여러 개를 둘 수 있도록 설계한 것입니다.

참고: 최근 Spring Boot에서는 이러한 복잡한 계층 설정을 내부적으로 자동화해주기 때문에, 개발자가 직접 web.xml을 만지며 계층을 나눌 일은 줄어들었지만, 내부 원리는 여전히 동일하게 작동합니다.

profile
패션과 개발을 좋아합니다.

0개의 댓글