이 두 가지 개념은 제어의 역전(IoC, Inversion of Control)이라는 스프링의 대원칙을 실현하는 가장 기초적인 구조이다.
먼저 이 두가지가 무엇인지 간단히 알아보면
1. 스프링 컨테이너 (Spring Container)
스프링 컨테이너는 자바 객체의 생명 주기를 관리하는 관리자라고 생각하면 편하다.
- 정의: 개발자가 작성한 코드의 처리 과정을 위임받아 독립적으로 처리하는 존재이다.
- 핵심 역할:
- 객체 생성: 개발자가 new 키워드로 객체를 직접 생성하지 않고, 컨테이너가 대신 생성한다.
- 관계 설정 (DI): 객체 간의 의존 관계(어떤 객체가 어떤 객체를 필요로 하는지)를 연결해 준다.
- 생명 주기 관리: 객체의 생성부터 소멸까지의 과정을 관리한다.
- 대표적인 인터페이스:
- ApplicationContext: 가장 많이 사용되는 컨테이너 형태이다. 단순한 객체 생성 외에도 국제화, 이벤트 발행 등 부가 기능을 제공한다.
- BeanFactory: 스프링 컨테이너의 최상위 인터페이스로, 빈을 조회하고 관리하는 기본적인 기능만 제공한다.
비유: 영화 촬영장으로 비유하자면, 스프링 컨테이너는 '영화 감독(또는 제작사)입니다. 배우(객체)를 캐스팅하고, 촬영 스케줄을 관리하며, 배우들끼리 호흡을 맞추게 지시합니다.
2. 스프링 빈 (Spring Bean)
스프링 빈은 스프링 컨테이너가 관리하는 자바 객체를 말한다.
- 정의: 스프링 컨테이너에 의해 생성되고, 관리되는 POJO(Plain Old Java Object)이다.
- 특징:
- 일반 객체와의 차이:
- MemberService service = new MemberService(); 이렇게 개발자가 직접 생성한 객체는 빈이 아니다.
- 스프링 설정 파일(XML)이나 어노테이션(@Component, @Bean)을 통해 컨테이너에 등록된 객체만 '빈'이라고 부른다.
- 싱글톤(Singleton): 기본적으로 스프링 빈은 컨테이너 내에서 단 하나의 인스턴스만 생성되어 관리된다. (요청할 때마다 새로 만드는 것이 아니라, 하나 만들어 놓고 공유해서 쓴다.)
- 일반 객체와의 차이:
비유: 다시 영화 촬영장으로 가면, 스프링 빈은 '배우' 입니다. 감독(컨테이너)의 관리를 받으며, 감독이 오라면 오고 가라면 가는 존재들이죠.
3. 왜 이 방식을 사용할까?
내가 공부할 때마다 가장 중요하게 생각하는 부분이다.
개발자가 직접 new를 써서 객체를 만들면 되는데, 왜 굳이 컨테이너에 빈으로 등록해서 쓸까?
- 의존성 주입(DI) 가능:
- 컨테이너가 객체 간의 관계를 맺어주기 때문에, 코드를 뜯어고치지 않고도 부품(객체)을 쉽게 교체할 수 있다. (예: MySQLRepository → OracleRepository로 변경 시 설정만 바꾸면 됨)
- 싱글톤 유지:
- 대규모 트래픽이 발생하는 웹 애플리케이션에서, 고객이 요청할 때마다 객체를 새로 만들면 메모리 낭비가 심하다. 컨테이너는 빈을 하나만 만들어서 효율적으로 재사용하게 해준다.
- 생명 주기 관리:
- 객체가 생성될 때 초기화 작업을 하거나, 서버가 종료될 때 연결을 안전하게 끊는 등의 작업을 컨테이너가 알아서 호출해 준다.
4. 빈을 등록하는 방법
빈을 등록하는 방법은 크게 두 가지가 있다.
A. 컴포넌트 스캔 (가장 많이 씀)
클래스 위에 @Component (또는 @Controller, @Service, @Repository)를 붙이면 스프링이 알아서 찾아서 빈으로 등록한다.
@Service // 이 어노테이션을 보고 컨테이너가 "아, 이건 내가 관리할 빈이구나" 하고 등록함
public class MemberService {
// ...
}
B. 자바 설정 파일 (Config)
@Configuration이 붙은 설정 클래스 안에서 @Bean을 사용하여 직접 등록한다.
@Configuration
public class AppConfig {
@Bean // memberService라는 이름의 빈으로 등록됨
public MemberService memberService() {
return new MemberService();
}
}
5. 전체적인 동작 흐름 요약
- 스프링 컨테이너 생성: 서버가 켜지면 스프링 컨테이너(ApplicationContext)가 생성
- 빈 등록: 컨테이너는 설정 정보(AppConfig.class 또는 @Component들)를 읽어서 빈 객체들을 생성
- 의존관계 주입 (DI): 생성된 빈들 사이에 필요한 연관 관계를 맺음 (예: Controller 안에 Service를 넣어줌)
- 초기화: 빈이 사용할 준비가 완료되었음을 알림
- 사용: 애플리케이션이 동작하면서 빈을 사용
- 소멸: 애플리케이션이 종료되면 빈도 안전하게 종료
6. 그럼 스프링이 해주니까 나는 그냥 가져다 쓰면 되겠네?
음... 뭐 틀린 말은 아니지만 공부를 하는 입장에서는 좋은 태도가 아니다.
결국 이 내부적으로 빈을 직접 조회하는 방법도 알야아 한다.
왜일까?
사실 실무에서 개발할 때 ac.getBean() 같은 메서드를 직접 호출해서 빈을 꺼내는 일은 거의 없다.
대부분 @Autowired나 생성자 주입을 통해 스프링이 알아서 넣어주는 기능을 사용한다.
"어차피 스프링이 알아서 해주는데, 왜 조회하는 규칙(타입 중복, 상속 관계 등)을 내가 알아야 하지?"라는 의문이 드는 게 당연할지도 모른다.
하지만 이 개념은 "에러를 해결하고, 스프링의 자동 주입 원리를 이해하기 위해" 반드시 필요해. 구체적으로 3가지 이유를 들어 보겠다.
6-1. 자동 주입(@Autowired)이 실패했을 때 해결하기 위해서 (가장 중요)
@Autowired는 마법이 아니라, 내부적으로 너가 배운 빈 조회 로직을 그대로 수행한다.
- 기본 로직: 타입으로 조회 (ac.getBean(MemberRepository.class))
- 문제 발생: 만약 MemberRepository 인터페이스를 구현한 빈이 MemoryMemberRepository, JdbcMemberRepository 두 개가 있다면?
- 결과: 스프링은 멍청해서 "어? 타입이 같은 게 2개네? 뭘 줘야 할지 모르겠어!" 하고 NoUniqueBeanDefinitionException이라는 에러를 터뜨리고 서버 켜지는 걸 거부한다.
이때 빈 조회 원리(타입 매칭, 필드 명 매칭 등)를 모르면 "왜 에러가 났지?" 하고 당황하게 된다. 반대로 원리를 알면, "아, 같은 타입이 2개라서 그렇구나. 그럼 @Primary를 쓰거나 @Qualifier로 이름을 지정해줘야겠다"라고 해결책을 바로 찾을 수 있다.
6-2. 다형성을 활용한 유연한 코드 설계 (Map, List 조회)
실무에서는 가끔 해당 타입의 빈을 모두 다 조회해야 하는 경우가 있다.
예를 들어, 할인 정책 서비스가 있다고 해보자.
- 클라이언트가 고정 할인(Fix)을 선택하면 FixDiscountPolicy를 쓰고,
- "정률 할인(Rate)"을 선택하면 RateDiscountPolicy를 쓰고 싶어.
이때 "특정 타입의 빈을 모두 조회하기" 기능에 대해 안다면, Map<String, DiscountPolicy> 형태로 모든 할인 정책 빈을 한 번에 주입받을 수 있다. 그러면 if-else 문을 복잡하게 안 쓰고도 키값(이름)만으로 원하는 로직을 쏙쏙 골라 쓸 수 있게 된다.
6-3. 테스트 코드 작성
애플리케이션 코드(Controller, Service)에서는 스프링이 알아서 주입해주지만, 테스트 코드를 짤 때는 상황이 다르다. 가끔은 내가 설정한 스프링 컨테이너가 제대로 떴는지, 내가 등록한 빈이 진짜로 들어있는지, 싱글톤이 깨지진 않았는지 직접 눈으로 확인(Assertions)해야 할 때가 있고 이때 ac.getBean()을 써서 직접 꺼내봐야 검증을 할 수 있다.
요약하자면
- 우리가 평소에 쓰는 @Autowired가 내부적으로 이 조회 규칙(타입 우선, 이름 보조 등)을 따른다.
- 따라서 빈이 2개 이상일 때 발생하는 에러를 이해하고 해결하려면 이 규칙을 알아야 한다.
- 나중에 고급 기능(전략 패턴 등)을 구현할 때 이 조회 능력이 무기가 된다.
결론: 직접 getBean()을 칠 일은 별로 없지만, "스프링이 어떻게 빈을 찾아서 꽂아주는지" 그 뇌구조를 이해하기 위해 배우는 과정이라고 생각하면 된다.
'tech > Spring' 카테고리의 다른 글
| [Spring] 인증(Authentication)과 인가(Authorization) (0) | 2025.12.03 |
|---|---|
| [Spring] SpringSecurity란? (0) | 2025.12.03 |
| [Spring] @Valid와 @Validated (1) | 2025.11.12 |
| Java에서 컬렉션 sort 동작 방식 (0) | 2025.11.05 |
| [Spring] @ActiveProfiles("test") vs @DataJpaTest (0) | 2025.08.21 |