Categories: Spring

Spring 스프링 컨테이너(Spring Container)란?

스프링 컨테이너(Spring Container)란?

스프링 컨테이너는 스프링 프레임워크에서 가장 핵심적인 부분으로, IoC(Inversion of Control) 개념을 기반으로 만들어진 의존성 관리 및 객체 생성, 설정, 생명주기 관리를 담당하는 중추적인 역할을 합니다. 쉽게 말해 스프링 컨테이너는 객체(빈, Bean)를 생성하고 관리하는 “공장(factory)”라고 볼 수 있습니다.

  • IoC(Inversion of Control): 제어의 역전이라는 말 그대로, 객체가 의존하는 객체(Bean)들을 직접 생성하고 관리하는 것이 아니라, 스프링 컨테이너가 대신 생성하고 연결(주입)해줍니다. 전통적인 방식에서 개발자가 new 키워드로 의존 객체를 직접 생성하던 부분이 역전되어, 스프링 컨테이너가 담당하게 되는 것이 핵심입니다.
  • DI(Dependency Injection): 의존관계 주입이라고 하며, IoC를 구현하는 방법 중 하나입니다. 스프링 컨테이너가 의존 객체를 대신 주입해주므로, 애플리케이션 코드가 의존 대상에 직접 접근하지 않아도 되며, 설정만으로도 주입 방식이 간편하게 변경될 수 있습니다.

스프링 컨테이너를 이용하면 결합도가 낮은(loose coupling) 애플리케이션 설계가 가능해집니다. 이는 유지보수성과 확장성에서 큰 이점을 제공합니다.

스프링 컨테이너의 종류

스프링 프레임워크에서 핵심적으로 제공하는 컨테이너는 크게 BeanFactoryApplicationContext로 나누어집니다.

  1. BeanFactory
    • 스프링의 가장 기본적인 컨테이너 인터페이스입니다.
    • 객체(빈)를 생성하고 의존관계를 설정하는 기능만 제공합니다.
    • 지연 로딩(lazy loading) 방식을 사용하여, 실제로 객체가 필요할 때에만 생성하는 특성이 있습니다.
    • 상대적으로 단순하고 경량이지만, 부가적인 기능(국제화, 환경변수 처리, AOP, 메시지 처리 등)은 지원하지 않습니다.
  2. ApplicationContext
    • BeanFactory를 확장한 가장 대표적이고 일반적으로 사용하는 스프링 컨테이너입니다.
    • BeanFactory가 제공하는 기능은 모두 포함하며, 추가 기능(국제화, 환경 변수, 애플리케이션 이벤트, AOP, 메시지 소스 처리, 프로파일 설정, 리소스 로딩 등)을 지원합니다.
    • 빈을 미리 생성(Eager loading)하여 런타임 시점 성능을 높일 수 있습니다.
    • 대표적인 구현체
      • ClassPathXmlApplicationContext: classpath에 있는 XML 파일에서 빈 정보를 로딩
      • FileSystemXmlApplicationContext: 파일 시스템 경로에 있는 XML 파일에서 빈 정보를 로딩
      • AnnotationConfigApplicationContext: 자바 애노테이션 기반 설정(@Configuration, @Bean 등)을 읽어 빈을 등록

실무에서는 일반적으로 ApplicationContext를 사용합니다. BeanFactory는 무척 간단한 테스트나 커스텀하게 최적화된 상황 이외엔 잘 쓰이지 않습니다.

스프링 컨테이너의 동작 과정

스프링 컨테이너의 전반적인 동작 흐름은 아래와 같습니다.

  1. 설정 정보 읽기
    • XML, 자바 클래스, 애노테이션 등을 통해 스프링 컨테이너가 빈 설정 정보를 읽어옵니다.
  2. 빈(Bean) 정의 등록
    • 스프링 컨테이너는 읽어온 설정 정보를 토대로 BeanDefinition(빈 정의) 객체를 생성하고 내부에 저장합니다.
    • BeanDefinition에는 빈의 클래스 정보, 스코프(싱글톤, 프로토타입 등), 의존관계 등에 대한 메타데이터가 들어 있습니다.
  3. 빈 생성 및 의존관계 주입
    • 스프링 컨테이너는 필요 시점에 빈을 생성하고, 의존관계를 자동 또는 수동으로 주입합니다.
    • 싱글톤 스코프의 빈은 일반적으로 컨테이너 초기화 시점에 모두 생성되고, 프로토타입 스코프의 빈은 요청 시점에 생성됩니다.
  4. 빈 초기화(Bean Initialization)
    • 빈이 생성된 후, 초기화 메서드(@PostConstruct 애노테이션 또는 init-method 지정 등)를 통해 빈이 내부적으로 필요한 작업을 수행합니다.
  5. 사용(Use)
    • 애플리케이션이 런타임 시점에서 빈을 사용합니다. 컨테이너는 의존성을 이미 주입해 두었으므로, 사용 시점에는 편리하게 활용할 수 있습니다.
  6. 빈 종료(Bean Destruction)
    • 컨테이너가 종료될 때, 소멸 메서드(@PreDestroy 애노테이션 또는 destroy-method 등)를 호출합니다.
    • 싱글톤 빈의 경우 주로 사용되며, 프로토타입 빈의 경우 컨테이너가 관리하지 않으므로 소멸 메서드가 호출되지 않습니다.

스프링 컨테이너 설정 방법

스프링 컨테이너에 빈 설정 정보를 주입하는 방법은 크게 XML 설정, 자바 설정, 애노테이션 설정 으로 나눌 수 있습니다. 실무에서는 주로 애노테이션 + 자바 설정 방식을 많이 활용합니다.

XML 기반 설정

과거 스프링 버전에서 가장 많이 사용되던 전통적 방식입니다. 빈의 의존관계를 명시적으로 관리하기 위해 XML 파일(applicationContext.xml 등)을 사용합니다.

XML
<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 빈(Bean) 정의 -->
    <bean id="helloService" class="com.example.service.HelloService" />

    <bean id="helloController" class="com.example.controller.HelloController">
        <!-- helloService 빈을 주입 -->
        <property name="helloService" ref="helloService" />
    </bean>

</beans>
XML

  • 장점: 설정이 명시적이고 직관적이어서, 외부에서 XML을 통해 전체 설정을 한눈에 파악하기 쉽습니다.
  • 단점: 설정 파일과 실제 코드가 분리되어 있으므로 IDE 자동완성 및 리팩토링이 어렵고, 설정 파일이 복잡해질수록 관리가 어려워집니다.

자바(AnnotationConfig) 기반 설정

XML 파일 대신 자바 클래스를 이용하여 설정할 수 있습니다. 스프링 부트가 적극적으로 추천하는 방법이기도 하며, 애노테이션을 통한 빈 등록을 함께 사용하는 것이 일반적입니다.

Java
@Configuration
public class AppConfig {
    
    @Bean
    public HelloService helloService() {
        return new HelloService();
    }

    @Bean
    public HelloController helloController() {
        // 의존성 주입
        return new HelloController(helloService());
    }
}
Java

AnnotationConfigApplicationContext를 통해 위 설정 클래스를 읽어들여 스프링 컨테이너가 빈을 생성하고 주입합니다.

Java
public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    HelloController controller = context.getBean(HelloController.class);
    controller.sayHello();
}
Java

  • 장점:
    • IDE 자동완성과 컴파일 체크를 통해 안정적인 리팩토링이 가능
    • 코드와 설정이 함께 있어, 유지보수가 용이
  • 단점: 설정(메서드)과 구현 로직이 같은 클래스로 섞일 경우, 구분이 헷갈릴 수 있음

애노테이션 기반 자동 빈 등록

  • @Component: 해당 클래스를 스프링 빈으로 등록하겠다는 표시
  • @Controller, @Service, @Repository: @Component를 확장한 대표적 애노테이션들로, 계층형 구조에서 역할을 나타낼 때 사용
  • @Autowired: 필요한 의존 객체를 자동으로 주입
Java
@Component
public class HelloService {
    public String greet(String name) {
        return "Hello, " + name;
    }
}

@Controller
public class HelloController {
    
    @Autowired
    private HelloService helloService;
    
    public void sayHello() {
        System.out.println(helloService.greet("Spring"));
    }
}
Java

  • @ComponentScan을 통해 특정 패키지 이하의 클래스 중 @Component 계열 애노테이션이 붙은 클래스를 자동으로 스캔해서 빈으로 등록합니다.
Java
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
}
Java

빈(Bean) 라이프사이클 상세

스프링 컨테이너에서의 빈 라이프사이클은 다음과 같습니다.

  1. 스프링 컨테이너 초기화
    • 설정 파일(또는 클래스, 애노테이션 등)을 읽고, BeanDefinition 정보를 생성합니다.
  2. 빈 생성(instantiate)
    • 싱글톤 스코프의 빈은 컨테이너 시작 시점에 생성(주입)됩니다.
    • 프로토타입 스코프의 빈은 요청이 있을 때마다 생성됩니다.
  3. 의존관계 주입(Dependency Injection)
    • @Autowired, @Resource, 생성자 주입, setter 주입 등 등록된 설정 방법대로 의존성이 주입됩니다.
  4. 빈 초기화(Bean Initialization)
    • @PostConstruct 애노테이션이 붙은 메서드나, XML 혹은 자바 설정에서 지정된 init-method가 실행됩니다.
    • 이 시점에서 DB 연결 풀 생성, 캐시 초기화 같은 작업을 주로 수행합니다.
  5. 사용(Use)
    • 애플리케이션 로직에서 빈을 자유롭게 사용합니다.
  6. 소멸(Destruction)
    • 컨테이너 종료 시점(@PreDestroy 또는 destroy-method를 통해) 빈이 정리됩니다.
    • 싱글톤 스코프 빈만 컨테이너가 관리하고, 프로토타입 스코프 빈은 사용 후 수동으로 정리해야 합니다.

빈 스코프(Bean Scope)

스프링 컨테이너는 빈마다 스코프(scope)를 지정할 수 있습니다. 스코프는 빈의 생존 범위를 뜻합니다.

  1. Singleton: 컨테이너에 한 개의 빈 인스턴스만 존재 (가장 기본값)
  2. Prototype: 빈을 요청할 때마다 새 인스턴스를 생성
  3. Request: 웹 요청마다 별도의 빈 인스턴스 생성 (Spring MVC 환경 등)
  4. Session: 웹 세션마다 별도의 빈 인스턴스 생성
  5. Application: 서블릿 컨텍스트(애플리케이션) 범위에서 단 하나의 빈 인스턴스 생성

실무에서 가장 많이 사용하는 스코프는 Singleton이고, 가끔 Prototype 스코프가 필요할 때도 있습니다. 웹 애플리케이션 환경이라면 Request, Session, Application 스코프도 사용할 수 있습니다.

간단한 예시: 자바 설정 + 애노테이션

아래 예시는 자바 기반 설정(@Configuration)과 애노테이션(@Component, @Autowired)을 활용하여 스프링 컨테이너에 빈을 등록 및 주입하는 과정을 보여줍니다.

프로젝트 구조
Markdown
com
 └─ example
     ├─ AppConfig.java
     ├─ service
     │   └─ HelloService.java
     └─ controller
         └─ HelloController.java
Markdown

코드
Java
// com/example/service/HelloService.java
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class HelloService {
    public String sayHello(String name) {
        return "Hello, " + name + "!";
    }
}
Java

Java
// com/example/controller/HelloController.java
package com.example.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import com.example.service.HelloService;

@Controller
public class HelloController {

    private final HelloService helloService;

    // 생성자 주입
    @Autowired
    public HelloController(HelloService helloService) {
        this.helloService = helloService;
    }

    public void processHello() {
        String greeting = helloService.sayHello("Spring");
        System.out.println(greeting);
    }
}
Java

Java
// com/example/AppConfig.java
package com.example;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
    // 필요한 경우 @Bean 정의를 추가로 넣을 수 있습니다.
}
Java

Java
// Main.java
package com.example;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.example.controller.HelloController;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = 
                new AnnotationConfigApplicationContext(AppConfig.class);

        HelloController controller = context.getBean(HelloController.class);
        controller.processHello();
    }
}
Java

  • 실행 시, 스프링 컨테이너가 AppConfig를 기반으로 @ComponentScan을 수행하여 @Controller와 @Service 등을 찾아 빈으로 등록합니다.
  • 생성자 주입(@Autowired)을 통해 HelloService 빈이 HelloController에 자동으로 전달됩니다.
  • controller.processHello()가 호출되면 “Hello, Spring!”이 출력됩니다.

스프링 컨테이너를 활용할 때 주의할 점

  1. 빈 싱글톤 주의
    • 대부분의 빈이 싱글톤 스코프로 관리되므로, 내부에 상태 값을 두는 것은 지양하는 것이 좋습니다.
    • 멀티쓰레드 환경에서 동시성 문제가 발생할 수 있기 때문입니다.
  2. 프로토타입 빈
    • 필요 시마다 매번 새 객체가 생성됩니다.
    • 컨테이너가 생성까지만 관여하고, 소멸 과정은 관여하지 않습니다.
  3. 순환 참조(Circular Reference)
    • A 빈이 B 빈을 참조하고, B 빈도 A 빈을 참조하는 순환구조가 생길 수 있습니다.
    • 스프링 5.3 부터 기본적으로 순환 참조를 막고 있으며(설정 변경 가능), 순환 참조를 피하는 설계를 권장합니다.
  4. 빈 생명주기 메서드
    • 초기화 메서드와 소멸 메서드를 잘 활용하면 리소스 초기화나 정리 로직을 깔끔하게 처리할 수 있습니다.
  5. 환경별 설정 분리
    • 개발/운영 환경별로 다른 설정을 사용해야 할 경우, 프로필(Profiles) 기능이나 설정 파일 분리를 통해 효율적으로 관리할 수 있습니다.

결론

스프링 컨테이너는 스프링 프레임워크의 핵심으로, 객체 생성 및 의존성 관리를 손쉽게 해 주며, 애플리케이션 개발자가 비즈니스 로직에만 집중할 수 있도록 돕습니다. IoCDI 개념을 이해하고, BeanFactoryApplicationContext의 차이, 각 빈의 스코프(scope)라이프사이클 등을 이해하면 스프링을 보다 유연하고 확장성 있게 사용할 수 있습니다.

오늘날 대부분의 스프링 프로젝트는 자바 기반 설정애노테이션을 많이 사용하지만, XML 기반 설정도 여전히 사용될 수 있으며, 프로젝트에 따라 적절히 혼합하여 쓰기도 합니다.

스프링 컨테이너를 제대로 이해하면 결합도를 낮춘 애플리케이션 아키텍처 설계가 가능하며, 다양한 부가 기능(AOP, 트랜잭션, 국제화, 테스트 지원 등)을 자유롭게 적용할 수 있다는 점에서 스프링 생태계 전체를 잘 이해하는 발판이 됩니다.

suover

Recent Posts

Java 자바 큐(Queue) 개념과 사용법

Queue란 무엇인가? Java에서 Queue는 데이터 구조의 일종으로, 데이터를 선입선출(FIFO, First-In-First-Out) 방식으로 처리합니다. 이 글에서는 Queue의…

3주 ago

Java 자바 스택(Stack) 개념과 사용법

Stack이란 무엇인가? Java에서 Stack은 자료구조의 한 종류로, 데이터를 순서대로 쌓아 올리는 형태로 운영됩니다. 컴퓨터 과학에서…

4주 ago

Java 자바 Map – HashMap, TreeMap, LinkedHashMap 정리

소개 자바에서 Map 인터페이스는 키(Key)와 값(Value)의 쌍을 저장하는 자료구조입니다. 이는 연관 배열이라고도 불리며, 각 키는…

1개월 ago

Java 자바 Set – HashSet, TreeSet, LinkedHashSet 정리

소개 자바에서 Set은 중복을 허용하지 않는 데이터 집합을 의미합니다. List와 달리 동일한 요소를 여러 번…

1개월 ago

Java 자바 Hash 해시 제대로 이해하기

해시(Hash)란 무엇인가? 해시(Hash)는 자바 프로그래밍에서 빠르고 효율적인 데이터 저장 및 검색을 위한 핵심적인 개념입니다. 이…

2개월 ago

인프런 워밍업 클럽 스터디 2기 – 백엔드 프로젝트 후기

입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기 강의와 함께한 인프런 워밍업 클럽…

2개월 ago