도입
스프링을 이해할 때 가장 먼저 정확히 잡아야 하는 개념이 바로 Bean입니다. 많은 입문자가 Bean을 단순히 “객체”라고 외우지만, 엄밀히 말하면 Bean은 스프링 컨테이너가 생성·보관·주입·생명주기 관리까지 담당하는 객체를 뜻합니다.
즉, 모든 객체가 Bean은 아닙니다. 자바에서 new로 직접 만든 객체는 그냥 일반 객체이고, 스프링 컨테이너 안에 등록되어 관리되는 객체만 Bean이라고 부릅니다.
정의
스프링에서 Bean은 단순히 메모리에 존재하는 인스턴스가 아니라, ApplicationContext(스프링 컨테이너)에 등록되어 관리되는 단위입니다. 컨테이너는 Bean의 생성 시점, 의존성 연결, 초기화, 소멸, 프록시 적용 여부까지 제어할 수 있습니다.
- Bean은 스프링 컨테이너가 관리하는 객체다.
- Bean은 단순 객체와 달리 생명주기와 의존성 주입의 대상이 된다.
- Bean은 애플리케이션의 기능을 구성하는 기본 조립 단위다.
필요성
Bean이 중요한 이유는 개발자가 직접 객체를 만들고 연결하는 부담을 줄여주기 때문입니다. 예전 방식처럼 클래스 안에서 다른 클래스를 new로 직접 생성하면 결합도가 높아지고, 테스트도 어려워집니다. 반면 스프링은 Bean을 통해 객체 생성 책임을 컨테이너로 넘기고, 필요한 의존성을 자동으로 주입합니다.
- 객체 생성 책임 분리: 개발자가 직접 생성하지 않아도 된다.
- 의존성 주입 지원: 필요한 객체를 자동 연결할 수 있다.
- 유지보수성 향상: 클래스 간 결합도를 낮출 수 있다.
- 테스트 용이성 증가: Mock 객체나 대체 구현체로 교체하기 쉽다.
- 생명주기 제어: 초기화/소멸 시점을 컨테이너가 관리한다.
일반 객체와 차이
| 구분 | 일반 객체 | Spring Bean |
|---|---|---|
| 생성 주체 | 개발자 | 스프링 컨테이너 |
| 의존성 연결 | 직접 연결 | 자동 주입 가능 |
| 생명주기 | 직접 관리 | 컨테이너가 관리 |
| AOP 적용 | 어려움 | 프록시 적용 가능 |
public class UserService {
private final UserRepository userRepository = new UserRepository();
}
위 코드는 UserRepository를 직접 생성하므로 일반 객체 사용 방식입니다. 반면 아래는 Bean을 주입받는 구조입니다.
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
등록 방식
import org.springframework.stereotype.Component;
@Component
public class MailSender { }
@Component, @Service, @Repository, @Controller 같은 어노테이션이 붙은 클래스는 컴포넌트 스캔 대상이 되고, 컨테이너에 Bean으로 등록됩니다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MailSender mailSender() {
return new MailSender();
}
}
외부 라이브러리 객체나 생성 로직을 직접 제어해야 하는 경우에는 @Bean으로 수동 등록하는 방식이 자주 쓰입니다.
생성·초기화·소멸
Bean은 등록된 뒤 끝나는 것이 아니라, 컨테이너 안에서 다음 순서로 관리됩니다.
- Bean 정의 등록: BeanDefinition이 컨테이너에 저장된다.
- 인스턴스 생성: 스프링이 객체를 만든다.
- 의존성 주입: 생성자/필드/세터를 통해 다른 Bean을 연결한다.
- 초기화 콜백: @PostConstruct 등이 실행될 수 있다.
- 사용: 애플리케이션이 Bean을 활용한다.
- 소멸 콜백: 컨텍스트 종료 시 @PreDestroy 등이 호출될 수 있다.
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class ConnectionManager {
@PostConstruct
public void init() {
System.out.println("초기화");
}
@PreDestroy
public void close() {
System.out.println("종료");
}
}
스코프
스프링 Bean의 기본 스코프는 singleton입니다. 즉, 같은 Bean 정의에 대해 보통 하나의 인스턴스만 생성되어 여러 곳에서 공유됩니다.
| 스코프 | 설명 |
|---|---|
| singleton | 기본값, 컨테이너당 하나의 인스턴스 |
| prototype | 요청할 때마다 새 객체 생성 |
| request | HTTP 요청마다 하나의 Bean |
| session | HTTP 세션마다 하나의 Bean |
의존성 주입과 관계
의존성 주입은 결국 “어떤 객체가 다른 객체를 필요로 할 때, 그 객체를 스프링이 대신 넣어주는 것”입니다. 이때 주입 대상도 Bean이어야 하고, 주입해주는 객체도 Bean이어야 합니다. 그래서 Bean 개념을 이해하지 못하면 DI도 정확히 이해하기 어렵습니다.
@Repository
public class UserRepository { }
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
위 코드에서 UserRepository와 UserService 모두 Bean이며, 스프링은 UserService를 생성할 때 필요한 UserRepository Bean을 자동으로 주입합니다.
@Component와의 관계
이 부분은 입문자들이 자주 헷갈립니다. Bean은 결과이고, @Component는 그 결과를 만들기 위한 등록 수단 중 하나입니다.
| 개념 | 의미 |
|---|---|
| Bean | 스프링 컨테이너가 관리하는 객체 |
| @Component | 클래스를 Bean으로 등록하기 위한 어노테이션 |
| @Bean | 메서드 반환 객체를 Bean으로 등록하는 방식 |
자주 하는 오해
- 오해 1: 모든 클래스를 Bean으로 만들어야 한다 → 아니다. 관리할 필요가 있는 구성요소만 Bean으로 둔다.
- 오해 2: Bean이면 무조건 싱글톤이 안전하다 → 아니다. 상태를 가지면 동시성 문제가 생길 수 있다.
- 오해 3: @Component와 Bean은 같은 말이다 → 아니다. 하나는 등록 수단, 하나는 관리 결과다.
- 오해 4: 필드 주입이 가장 편하니 가장 좋다 → 아니다. 실무에서는 생성자 주입이 더 권장된다.
디버깅
요약
- ✅ Bean은 스프링 컨테이너가 관리하는 객체다.
- ✅ 모든 객체가 Bean은 아니며, 등록된 객체만 Bean이다.
- ✅ Bean은 DI의 대상이며 애플리케이션 조립의 기본 단위다.
- ✅ @Component와 @Bean은 Bean 등록 방식이고, Bean은 그 결과물이다.
- ✅ Bean의 기본 스코프는 singleton이며 상태 관리에 주의해야 한다.