
1. 들어가며
스프링을 쓰다 보면 빈(Bean)이라는 말을 정말 자주 듣는다.
“스프링이 빈을 관리해준다”, “빈으로 등록해야 한다”, “빈 주입이 안 된다”…
근데 솔직히 처음엔 이런 생각이 든다.
“그냥 new로 객체 만들면 되는 거 아닌가?”
“굳이 Bean으로 등록해야 하는 이유가 뭐야?”
나도 그랬다. (나는 그랬다..비전공자 입장에서 Bean이라는게 싫었다..)
그냥 자바 객체니까 new 해서 쓰면 되지,
뭘 ‘등록’까지 해야 하나 싶었다.
그런데 실제로 스프링을 깊게 쓰다 보면,
“Bean으로 관리해야만 하는 이유”가 명확하게 보인다.
2. Bean이란 무엇인가?
Bean은 간단히 말해서 “스프링이 대신 관리해주는 객체”다.
즉, 우리가 new로 직접 만들지 않고,
스프링이 대신 만들고, 대신 주입해주고, 대신 소멸시켜주는 객체.
그렇다면 “관리해준다”는 게 구체적으로 뭘까?
그건 아래 세 가지를 의미한다.
1.생명주기 관리
- 객체를 생성하고, 의존성을 주입하고, 초기화하고, 소멸시킬 때까지 관리
2.의존성 관리 (Dependency Injection)
- 서로 연결되어야 하는 객체들을 알아서 주입
3. AOP / 트랜잭션 / 캐싱 같은 기능 자동 적용
- 스프링이 Bean을 감싸면서 추가 기능을 붙여줌 (Proxy 기반)
즉, Bean은 단순한 객체가 아니라,
스프링이 개입할 수 있는 통제 가능한 단위 이다.
예를 들어 이런 코드가 있다고 하자
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
여기서 UserService, UserRepository 둘 다 Bean이다.
스프링이 앱 시작 시점에 알아서 만들어서 메모리에 올려둔다.
그럼 질문은 여기서 시작된다.
“왜 스프링이 대신 만들어야 하지?”
“내가 직접 new 하면 뭐가 문제야?”
지금부터 차근차근 풀어보겠다.
3. 왜 Bean을 써야하는가? 왜 존재해 대체?
결론부터 말하자면,
Bean을 사용하는 이유는 객체를 효율적으로, 일관되게 관리하기 위해서다.
1) 스프링은 Bean을 싱글톤(Singleton) 으로 관리한다
가장 큰 이유는 바로 이거다.
동일한 객체를 여러 곳에서 재사용하기 위해서.
예를 들어 UserRepository라는 객체를 생각해보자.
이게 각 서비스마다 매번 새로 생성된다면?
new UserRepository();
new UserRepository();
new UserRepository();
이렇게 계속 생성되면,
- 메모리 낭비
- 커넥션 풀 등 리소스 관리 불안정
- 트랜잭션 불일치
중요 정리)
만약 UserRepository객체가 여러개면..? 다양한 객체에 유저들의 정보가 중구난방으로
저장될 것이고, 그럼 관리하기도 어려워지며 데이터의 무결성을 해칠 수 있다.
그래서 스프링은 컨테이너(ApplicationContext) 안에서
“한 클래스 당 하나의 인스턴스만” 만들어둔다.
즉, Repository, Service, Component 같은 Bean은
애플리케이션 전역에서 공유되는 하나의 객체로 관리된다.
이게 바로 싱글톤 패턴을 프레임워크 차원에서 보장하는 구조다.
2) 스프링이 대신 의존성을 주입해준다 (DI — Dependency Injection)
객체 간 의존관계도 직접 연결하지 않고,
스프링이 Bean들끼리 알아서 묶어준다.
즉,
“A가 B를 필요로 하면, new로 만드는 게 아니라
스프링이 관리하는 B를 찾아서 자동으로 넣어준다.”
이게 바로 우리가 흔히 쓰는 @Autowired, @Inject, @RequiredArgsConstructor 같은 기능이다.
덕분에 코드가 이렇게 간결해진다.
@Service
public class OrderService {
private final UserRepository userRepository;
@Autowired
public OrderService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
직접 연결하는 대신,
스프링이 Bean 목록에서 UserRepository를 찾아 넣어준다.
이게 가능한 이유는 UserRepository가 Bean으로 등록되어 있기 때문이다.
2) 스프링이 대신 의존성을 주입해준다 (DI — Dependency Injection)
예를 들어 트랜잭션, 로깅, 캐싱, 시큐리티 등은 전부 AOP 기반으로 돌아간다.
그리고 AOP는 Bean을 Proxy로 감싸는 방식으로 동작한다.
즉, 스프링이 Bean을 직접 생성하고 감싸지 않으면,
이런 기능들이 전혀 작동하지 않는다.
Bean = 스프링이 기능을 “끼워 넣을 수 있는 확장 포인트”
그래서 @Transactional, @Async, @Cacheable 같은 기능이
직접 new 한 객체에서는 절대 동작하지 않는다.
4. Bean을 등록하는 이유와 방법
그럼 왜 “등록”이란 걸 해야 할까?
이유는 단순하다.
스프링에게 “이 객체를 내가 직접 만들지 말고, 네가 대신 관리해줘”
라고 알려주는 과정이 바로 ‘등록’이다.
등록 방법은 두 가지다.
1) 자동 등록
@Component
@Service
@Repository
@Controller
이런 어노테이션이 붙은 클래스는
컴포넌트 스캔(Component Scan)을 통해 자동으로 Bean으로 등록된다.
2) 수동 등록
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService(userRepository());
}
}
외부 라이브러리처럼 직접 수정할 수 없는 클래스를 Bean으로 쓰고 싶을 때,
@Bean으로 등록해준다.
이 과정을 거쳐야 스프링이 해당 객체를 “관리 목록”에 올리고,
필요할 때 @Autowired로 주입할 수 있다.
5. Bean이 없다면 생기는 문제들
만약 Bean을 쓰지 않고, 전부 직접 new 해서 관리한다면?
| 문제 | 설명 |
| 객체 중복 생성 | 매번 new 하므로 메모리 낭비 |
| 트랜잭션 불일치 | Repository가 여러 개면 동일 DB 트랜잭션을 공유 불가 |
| DI 불가능 | @Autowired, @Value 등 의존성 주입 불가 |
| 확장성 저하 | AOP, 로깅, 캐시 기능 적용 불가 |
| 테스트 어려움 | Mock 주입, Bean 교체 불가능 |
결국 Bean은 단순한 편의 기능이 아니라,
스프링 아키텍처의 기반 구조다.
6. 정리
.Bean은 단순히 “객체”가 아니라 스프링이 세상을 제어하는 단위다.
Spring은 Bean을 통해 객체의 생명주기를 관리하고, 의존성을 연결하고,
트랜잭션과 AOP를 적용하며, 무엇보다 “하나의 객체를 여러 곳에서 안전하게 공유”하게 만든다.
결국 Bean은 스프링의 기본 단위이자, 철학의 중심이다.
“스프링은 Bean으로 시작해서 Bean으로 끝난다.”
'JAVASPRING > study' 카테고리의 다른 글
| 영속성 컨텍스트 (0) | 2025.10.15 |
|---|---|
| N+1 문제 (3) | 2025.09.26 |
| Proxy 객체란? (1) | 2025.09.23 |
| H2 database, 개념 잡기 (0) | 2024.06.27 |
| GlobalException (0) | 2024.06.21 |