본문 바로가기
Daily Dev Q&A 정리 템플릿

25.12.26 Spring Bean은 무엇인가요?

by teg0 2025. 12. 26.

Bean이라는 단어를 볼 때 일반 사람들은 콩이라는 단어를 먼저 떠올릴 수 있지만, 개발자 분들은 스프링의 Bean을 먼저 떠올 일 것이다. 

 

왜 빈(Bean)이라고 정한 걸까?

 

빈으로 작명하기 전에 왜 자바로 작명했는지를 알아야 한다.

자바를 개발한 팀이 즐겨 마시던 인도네시아 자바 섬의 커피에서 유래된 것이다.

 

그리고 자바 객체는 자바 빈이라고 짓고, 스프링이 등장하면서, 스프링 빈이라고 부르기 시작되었다.

이 스프링 빈은 단순하고 가벼운 객체라는 뜻을 함축하기 위해서 Bean이라는 단어를 사용했다.

 

스프링 빈(Bean)

스프링을 공부하다 보면 가장 많이 듣게 되는 단어가 바로 빈(Bean)이다.

아주 쉽게 정의하자면, "스프링 컨테이너가 생성하고 관리하는 자바 객체"를 의미한다.

 

단순히 우리가 new MemberService()처럼 직접 만든 객체는 빈이 아니다.

스프링이 대신 만들어주고, 조립해 주고, 관리해 주는 객체만이 '스프링 빈'이라는 자격을 얻는다.

 

빈의 역할

의존성 주입(DI)의 단위 (Lego Block)

빈의 가장 중요한 역할은 애플리케이션을 구성하는 부품이 되는 것이다.

  • 역할: 객체와 객체가 서로 결합할 때, 내가 직접 상대 객체를 찾아가는 것이 아니라 스프링에 의해 수동적으로 주입받는 대상이  된다.
  • 이점: 빈으로 등록되어 있어야만 다른 객체에 주입될 수 있고, 나 또한 다른 빈을 주입받을 수 있다.
    이를 통해 객체 간의 결합도를 낮추는 핵심 부품 역할을 한다.

 

효율적인 자원 관리 (Singleton)

빈은 기본적으로 싱글톤(Singleton)으로 관리된다.

  • 역할: 애플리케이션 전체에서 특정 기능을 수행하는 객체를 단 하나만 생성하여 공유하게 한다.
  • 이점: 예를 들어, 수만 명의 사용자가 동시에 접속하는 서비스에서 매번 MemberService 객체를 새로 만든다면 메모리 부하가 엄청날 것이다. 빈은 이를 하나만 유지하며 재사용하게 함으로써 시스템 자원을 효율적으로 관리한다.

 

생명주기 제어 (Life Cycle Management)

빈은 스프링 컨테이너의 통제 하에 안전하게 생성되고 소멸된다.

  • 역할: 단순한 자바 객체는 생성되고 나면 가비지 컬렉터(GC)에 의해 언제 사라질지 알기 어렵다.
    하지만 빈은 "지금 바로 연결해(Init)", "이제 안전하게 닫아(Destroy)"와 같은 명령을 수행할 수 있다.
  • 이점: 데이터베이스 연결이나 네트워크 소켓 관리처럼 정교한 뒷정리가 필요한 작업을 빈이 대신 책임지고 수행한다.

 

부가 기능의 적용 대상 (AOP Proxy)

빈으로 등록되면 스프링의 AOP(관점 지향 프로그래밍) 기술을 적용받을 수 있다.

  • 역할: 내가 만든 비즈니스 로직(빈)의 앞뒤에 트랜잭션 처리, 로그 기록, 보안 체크 같은 공통 기능을 스프링이 대신 붙여준다.
  • 이점: 개발자는 핵심 로직만 짜면 된다. 스프링은 해당 객체가 빈이라면 자동으로 감싸서(Proxy) 트랜잭션을 걸어주거나 실행 시간을 측정해 주는 등의 마법을 부릴 수 있다.

 

빈을 등록하는 방법 (어떻게 빈이 될까?)

스프링에게 "이 클래스를 빈으로 관리해 줘!"라고 말하는 방법은 크게 두 가지 있다.

 

컴포넌트 스캔 (자동)

클래스 위에 @Component (또는 이를 포함한 @Service, @Controller 등) 어노테이션을 붙인다.

스프링이 구동될 때 패키지를 훑으며 이 표시가 붙은 클래스들을 자동으로 빈으로 등록한다.

 

자바 설정 파일 (수동)

설정 클래스(@Configuration) 내에서 메서드에 @Bean 어노테이션을 붙여 직접 반환하는 방식이다.

  • 언제 쓰나요? 외부 라이브러리 객체를 빈으로 등록해야 할 때(코드를 수정할 수 없으므로), 혹은 공통 설정을 한눈에 관리하고 싶을 때 사용한다.
@Configuration
public class AppConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate(); // 외부 라이브러리 객체를 빈으로 등록
    }
}

 

빈의 범위 (Bean Scope)

빈이 언제까지 살아있고, 몇 개나 생성될지를 결정한다.

스코프 특징
싱글톤 (Singleton) 기본값. 컨테이너에 딱 하나만 생성되어 모두가 공유한다.
프로토타입 (Prototype) 빈을 요청할 때마다 새로운 객체를 생성하여 반환한다.
웹 관련 스코프 request(HTTP 요청마다), session(사용자 세션마다) 등이 있다.

 

중요: 스프링 빈은 기본적으로 싱글톤이다. 따라서 여러 스레드에서 동시에 접근할 수 있으므로, 빈 내부의 상태를 변경하는 필드 변수를 두지 않는 무상태(Stateless) 설계가 필수적이다.

 

무상태는 "객체가 이전의 상태를 기억하지 않는다."라는 뜻이다.

 

상태 유지와 무상태

상태 유지 (Stateful): 객체가 특정 클라이언트의 정보를 필드 변수에 저장하고 있는 상태.

  • 비유: 개인 비서. "아까 주문한 거랑 똑같은 걸로 줘"라고 하면 비서는 내가 누군지, 뭘 주문했는지 기억하고 있다.

무상태 (Stateless): 객체가 클라이언트의 정보를 저장하지 않는다. 모든 요청은 독립적이며, 필요한 데이터는 요청 시점에 직접 전달받아야 한다.

  • 비유: 자판기. 누가 버튼을 누르든 상관없이 돈이 들어오고 버튼이 눌려야만 작동한다. 자판기는 앞사람이 누구였는지 기억하지 않는다.

 

왜 스프링 빈은 무상태여야 할까? (문제 상황)

만약 싱글톤인 @Service 클래스가 상태를 유지(Stateful)하면 어떤 대참사가 일어나는지 코드로 보겠다.

 

❌ 위험한 코드 (Stateful)

@Service
public class OrderService {
    private int price; // 특정 사용자의 주문 금액을 저장하는 필드 (상태 유지!)

    public void order(String name, int price) {
        System.out.println("사용자: " + name + ", 주문 금액: " + price);
        this.price = price; // 여기서 상태가 저장됨!
    }

    public int getPrice() {
        return price;
    }
}

사고 시나리오:

  1. 사용자 A가 10,000원짜리 물건을 주문한다. -> orderService.order("A", 10000) 호출.
  2. 서비스의 price 필드는 이제 10,000이 된다.
  3. 그 직후, 사용자 B가 20,000원짜리 물건을 주문한다. -> orderService.order("B", 20000) 호출.
  4. 싱글톤이므로 A와 B는 같은 객체를 쓴다. -> 이제 price는 20,000으로 덮어씌워진다.
  5. 사용자 A가 "내 주문 금액이 얼마죠?"라고 물어본다. -> getPrice()를 호출하면 20,000원이 반환된다! (A는 10,000원을 결제해야 하는데)

 

이런 사고를 막기 위해 스프링 빈은 반드시 다음과 같이 설계해야 한다.

✅ 안전한 코드 (Stateless)

@Service
public class OrderService {
    // 특정 상태를 저장하는 필드를 없애고, 
    // 결과를 바로 반환하거나 파라미터로 처리합니다.

    public int order(String name, int price) {
        System.out.println("사용자: " + name + ", 주문 금액: " + price);
        return price; // 상태를 저장하지 않고 바로 반환
    }
}

 

  • 필드 변수(멤버 변수)에 공유 데이터를 저장하지 않는다.
  • 지역 변수, 파라미터, ThreadLocal 등을 사용한다. (이들은 각 스레드마다 독립적인 메모리 공간을 가진다.)
  • 값의 수정이 없는 읽기 전용 필드만 허용한다. (예: 주입받은 다른 빈의 참조값)

 

빈의 생명주기 (Lifecycle)

스프링 빈(Bean)의 생명주기(Lifecycle)를 아는 것은 객체의 생성부터 소멸까지의 과정을 제어할 수 있다는 뜻이다.

특히 데이터베이스 커넥션 풀이나 네트워크 소켓처럼, 시작 시점에 연결을 맺고 종료 시점에 연결을 안전하게 끊어야 하는 작업을 할 때 필수적인 지식이라고 볼 수 있다.

 

스프링 빈의 전체 생명주기 흐름

[ 스프링 컨테이너 생성 ]

↓

1. 빈 인스턴스화 (Instantiation): 자바 객체 생성 (생성자 호출)

2. 의존관계 주입 (Populate Properties): @Autowired 등으로 의존성 연결

3. Aware 인터페이스 호출: 빈이 자신의 이름이나 컨테이너 정보를 알 수 있게 함

4. 빈 초기화 전 처리 (PostProcessBeforeInitialization): BeanPostProcessor가 동작

5. 초기화 콜백 (Initialization): 객체가 사용될 준비를 마침

6. 빈 초기화 후 처리 (PostProcessAfterInitialization): AOP 프록시 생성 등이 여기서 일어남

↓

[ 빈 사용 ]

↓

7. 소멸 전 콜백 (Destruction): 컨테이너 종료 직전 리소스 정리

↓

[ 스프링 종료 ]

 

 

핵심 단계

인스턴스화 vs 의존관계 주입

  • 생성자 주입: 객체를 만드는 시점에 의존성 주입이 동시에 일어난다. (1단계와 2단계가 결합됨)
  • 필드/수정자 주입: 객체를 먼저 만들고 나서 의존성을 나중에 꽂아준다. (1단계와 2단계가 명확히 분리됨)

초기화(Initialization) 단계

  • 빈이 생성되고 의존성 주입까지 완료된 후, "이제 일을 시작해도 좋다"라고 알려주는 단계이다.
  • 예: DB 커넥션 풀이 실제 DB와 연결을 맺는 시점.

소멸(Destruction) 단계

  • 애플리케이션이 종료될 때 빈이 안전하게 소멸되는 단계이다.
  • 예: 맺어놓은 DB 연결을 끊거나, 열려있는 파일을 닫는 시점.

 

빈 생명주기 콜백을 받는 3가지 방법

방법 1: 어노테이션 방식 (권장 ⭐)

가장 간편하고 스프링에서도 공식적으로 권장하는 방식으로 자바 표준(JSR-250)이라 스프링이 아닌 다른 환경에서도 동작한다.

@Component
public class NetworkClient {
    
    @PostConstruct
    public void init() {
        System.out.println("연결 시작: 서버로 요청을 보낼 준비가 되었습니다.");
    }

    @PreDestroy
    public void close() {
        System.out.println("연결 종료: 안전하게 리소스를 반납합니다.");
    }
}

 

방법 2: 설정 정보에 초기화/소멸 메서드 지정

코드를 수정할 수 없는 외부 라이브러리를 빈으로 등록할 때 사용한다.

@Configuration
public class AppConfig {
    @Bean(initMethod = "init", destroyMethod = "close")
    public ExternalLibrary externalLibrary() {
        return new ExternalLibrary();
    }
}

 

방법 3: 인터페이스 상속 (InitializingBean, DisposableBean)

스프링 초기에 사용하던 방식으로, 코드가 스프링 전용 인터페이스에 의존하게 되어 현재는 거의 사용하지 않는다.

 

 

면접 답변식 요약

스프링 빈은 스프링 IoC 컨테이너가 생성, 의존성 주입, 생명주기를 관리하는 자바 객체를 말합니다. 일반적인 객체와 달리 개발자가 직접 new 키워드로 생성하지 않고 컨테이너가 제어권을 가집니다.

빈은 기본적으로 싱글톤 범위로 관리되어 메모리 효율을 높이며, @Component 스캔이나 @Configuration의 @Bean 설정을 통해 등록할 수 있습니다. 특히 @PostConstruct와 @PreDestroy 같은 콜백 메서드를 통해 객체의 생성부터 소멸까지 안전하게 관리할 수 있다는 점이 가장 큰 특징입니다.