-
2 - 스프링 컨텍스트 빈 정의Spring/스프링 교과서 2026. 7. 13. 22:51
객체를 Spring Bean으로 만든다는 것은 단순히 인스턴스를 컨테이너에 넣는 것이 아니라, 스프링이 그 객체를 어떻게 만들고 식별하고 관리할지 알 수 있도록 Bean Definition을 등록하는 일입니다.
시작하며
다음 코드는 자바 객체를 하나 만듭니다.
PaymentClient paymentClient = new HttpPaymentClient();하지만 이 객체를 생성했다고 해서 스프링이 자동으로 알게 되는 것은 아닙니다. 스프링이 의존성을 주입하고 생명주기를 관리하며 AOP 후처리를 적용하려면 먼저 이 객체에 대한 정의가
ApplicationContext에 등록되어야 합니다.처음에는 스프링 컨텍스트를 Bean을 담는 바구니로 이해해도 괜찮다. 다만 더 정확히 말하면 컨테이너는 완성된 객체만 보관하는 상자라기보다 객체를 생성하고 관리하기 위한 설명서인 Bean Definition과, 그 설명서로 만들어진 Bean을 관리하는 곳에 가깝습니다.
이 장에서 중요한 질문은
이 객체를 어떤 방식으로 스프링의 관리 대상으로 등록할 것인가?
입니다.
등록 방식은 세 가지입니다.
- 구성 클래스에서
@Bean메서드로 직접 등록합니다. @Component계열 애너테이션을 붙이고 컴포넌트 스캔으로 발견하게 합니다.- 조건과 데이터를 코드로 판단해 프로그래밍 방식으로 등록합니다.
1.
@Bean: 객체 생성 과정을 내가 직접 설명한다
@Configuration은 이 클래스가 Bean Definition의 출처라는 뜻이고,@Bean은 해당 메서드가 반환하는 객체를 스프링이 관리하게 합니다.@Configuration class PaymentConfig { @Bean PaymentClient paymentClient() { return new HttpPaymentClient(); } }스프링은
paymentClient()를 평범한 비즈니스 메서드가 아니라 Bean을 만드는 팩터리 메서드로 해석합니다. 기본적으로 메서드 이름인paymentClient가 Bean 이름이 되고, 반환 타입과 생성 방법 등의 정보가 Bean Definition에 반영됩니다.이 방식의 장점은 인스턴스를 만드는 과정을 코드로 완전히 제어할 수 있다는 것입니다.
- 외부 라이브러리 클래스처럼 소스에
@Component를 붙일 수 없는 객체도 등록할 수 있습니다. - 생성자에 복잡한 설정값을 전달하거나 팩터리 메서드를 호출할 수 있습니다.
- 같은 타입의 Bean을 서로 다른 이름과 설정으로 여러 개 정의할 수 있습니다.
반면 Bean마다 팩터리 메서드를 작성해야 하므로 등록할 객체가 많으면 구성 코드가 길어질 수 있습니다.
@Configuration과@Bean은 무슨 관계일까@Bean메서드는 일반@Component클래스에도 선언할 수 있습니다. 하지만 보통은@Configuration안에서 사용합니다.@Configuration의 기본 모드에서는 같은 구성 클래스 안의@Bean메서드 호출을 컨테이너가 가로채 Scope와 생명주기 규칙을 지킨다. 반대로@Configuration(proxyBeanMethods = false)또는 일반 컴포넌트의@Bean은 이른바 lite mode로 처리되어 메서드 직접 호출이 평범한 자바 호출처럼 동작합니다.그래서 최근에는 Bean 사이의 의존성을 메서드 직접 호출보다 매개변수로 드러내는 방식이 이해하기 쉽습니다.
@Bean OrderService orderService(PaymentClient paymentClient) { return new OrderService(paymentClient); }같은 타입의 Bean이 여러 개라면

같은 인터페이스를 구현하는 Bean을 여러 개 등록하는 것은 자연스럽습니다.
@Configuration class PaymentConfig { @Bean PaymentClient cardPaymentClient() { return new CardPaymentClient(); } @Bean PaymentClient bankPaymentClient() { return new BankPaymentClient(); } }문제는 등록이 아니라 단일
PaymentClient가 필요한 주입 지점에서 무엇을 선택할지입니다. 타입만으로 후보를 하나로 좁힐 수 없으면 스프링은 임의로 고르지 않고 예외를 발생시킵니다.해결 방법은 의도를 명시하는 것입니다.
- 기본 후보 하나를 정한다:
@Primary - 주입 지점과 후보를 세밀하게 연결한다:
@Qualifier - 이름과 타입으로 직접 조회한다:
getBean("cardPaymentClient", PaymentClient.class) - 후보 전체가 필요하면
List<PaymentClient>또는Map<String, PaymentClient>로 받는다.
@Primary는 같은 타입의 Bean 중 하나를 없애거나 나머지를 무효로 만드는 애너테이션이 아닙니다. 단일 의존성을 결정할 때 우선권을 주는 표시다. 컬렉션으로 주입하면 타입에 맞는 Bean은 모두 포함됩니다.2. 컴포넌트 스캔: 클래스를 표시하고 스프링이 발견하게 한다

애플리케이션이 직접 소유한 클래스라면 스테레오타입 애너테이션이 편리합니다.
@Service class OrderService { }@Configuration @ComponentScan(basePackages = "com.example.order") class OrderConfig { }동작은 두 단계로 볼 수 있습니다.
- 클래스에
@Component,@Service,@Repository,@Controller같은 스테레오타입을 붙여 후보임을 표시합니다. @ComponentScan이 지정한 패키지를 탐색해 후보 클래스의 Bean Definition을 컨텍스트에 등록합니다.
@Service,@Repository,@Controller도 결국@Component를 메타 애너테이션으로 가집니다. 기능상 모두 스캔 대상이지만 계층의 역할을 드러내므로 상황에 맞는 애너테이션을 사용하는 편이 좋습니다. 특히@Repository는 데이터 접근 예외 변환과도 연결됩니다.스테레오타입은 같은 타입의 Bean을 하나만 만들 수 있을까
이 표현은 조건을 붙여야 정확합니다.
컴포넌트 스캔은 기본적으로 발견한 컴포넌트 클래스마다 하나의 Bean Definition을 등록합니다. 따라서 하나의 구체 클래스에
@Component를 붙여 스캔하면 그 클래스에 대응하는 정의가 기본적으로 하나 생깁니다.하지만 스테레오타입 방식 자체가 같은 타입의 Bean 여러 개를 금지하는 것은 아닙니다. 같은 인터페이스를 구현한 여러 컴포넌트가 각각 발견될 수도 있고, Scope나 다른 등록 방식을 조합할 수도 있습니다. 핵심은 “스캔된 클래스 하나당 기본 Bean Definition 하나”라고 이해하는 것입니다.
@PostConstruct는 등록 방식이 아니다@PostConstruct는 Bean을 컨텍스트에 등록하는 애너테이션이 아닙니다. Bean이 생성되고 의존성 주입이 끝난 뒤 초기화 작업을 실행하는 생명주기 콜백입니다.@Component class CacheWarmup { @PostConstruct void load() { // 초기 데이터 적재 } }초기화 실패가 애플리케이션 시작 실패로 이어져도 되는지, 외부 시스템 호출 때문에 시작 시간이 길어지지는 않는지까지 함께 판단해야 합니다.
@Bean과 컴포넌트 스캔은 언제 선택할까기준 @Bean컴포넌트 스캔 생성 과정 직접 제어한다 기본적으로 컨테이너가 생성한다 대상 클래스 외부 라이브러리 포함 주로 직접 수정 가능한 애플리케이션 클래스 같은 구체 클래스의 여러 설정 명시적으로 만들기 쉽다 기본 스캔만으로는 한 정의가 생긴다 코드량 Bean마다 메서드가 필요하다 후보 클래스에 애너테이션을 붙이면 된다 사용하기 좋은 곳 인프라 설정, 외부 SDK, 복잡한 생성 Controller, Service, Repository 등 애플리케이션 구성 요소 실무에서는 둘 중 하나만 고집하지 않습니다. 직접 작성한 비즈니스 컴포넌트는 스캔으로 등록하고,
DataSource, HTTP Client, 외부 SDK처럼 생성 설정을 명시해야 하는 객체는@Bean으로 등록하는 조합이 흔합니다.3. 프로그래밍 방식: 등록 조건 자체가 동적일 때 사용한다

등록할 Bean의 수나 종류가 런타임 입력, 설정 파일 또는 반복문 결과에 따라 달라진다면 프로그래밍 방식이 필요할 수 있습니다.
var context = new AnnotationConfigApplicationContext(); for (ClientProperties client : clients) { context.registerBean( client.name(), PaymentClient.class, () -> new HttpPaymentClient(client.baseUrl()) ); } context.refresh();registerBean()의 주요 입력은 다음과 같습니다.- Bean 이름
- Bean 클래스
- 인스턴스를 만드는
Supplier - Primary, Lazy 같은 Bean Definition 속성을 바꾸는 Customizer
context.registerBean( "primaryPaymentClient", PaymentClient.class, () -> new HttpPaymentClient(baseUrl), definition -> definition.setPrimary(true) );이 방식은 유연하지만 일반적인 서비스 객체까지 모두 코드로 등록하면 구성을 한눈에 파악하기 어려워집니다. 등록 규칙이 데이터 기반이거나 반복적일 때 사용하는 편이 목적에 맞습니다.
Spring Framework 7에는 프로그램 등록을 캡슐화하는
BeanRegistrar와BeanRegistryAPI도 추가되었습니다. 다만 이 장의 핵심은 특정 API 이름보다 “선언만으로 표현하기 어려운 등록 규칙을 코드로 만들 수 있다”는 점입니다.세 방법을 하나의 원리로 묶기
겉으로는 애너테이션과 메서드가 서로 달라 보이지만 컨테이너 관점에서는 같은 목적을 가진다.
@Bean 메서드 ─────────────┐ │ 컴포넌트 스캔 -────────────┼─→ Bean Definition 등록 │ ↓ registerBean() ──────────┘ Bean 생성·조립·관리중요한 것은 객체를 누가
new했는지만 보는 것이 아닙니다. 스프링이 객체의 생성 방법과 식별자, Scope, 의존 관계 같은 메타데이터를 알고 관리 흐름에 참여하는지를 봐야 합니다.결론
스프링에게 객체를 관리해 달라고 하려면 그 객체의 존재와 생성 방법을 Bean Definition으로 알려 줘야 합니다. @Bean, 컴포넌트 스캔, 프로그래밍 등록은 그 정의를 전달하는 서로 다른 방법입니다.
이 관점이 잡히면
@Bean과@Component를 단순히 “둘 다 Bean을 만드는 애너테이션”으로 외우지 않게 됩니다. 어떤 객체를 등록하는지, 생성 과정을 얼마나 제어해야 하는지, 등록 규칙이 정적인지 동적인지를 보고 방식을 선택할 수 있습니다.추가 질문
@Component와@Bean의 가장 큰 차이는 무엇인가요?@Component는 클래스 자체를 스캔 후보로 표시하고,@Bean은 개발자가 작성한 팩터리 메서드의 반환 객체를 등록합니다. 수정할 수 없는 외부 클래스나 생성 로직을 직접 통제해야 하는 객체에는@Bean이 적합합니다.같은 타입의 Bean이 여러 개면 어떻게 되나요?
컬렉션 주입에는 모두 들어갈 수 있지만 단일 타입 주입은 후보를 하나로 결정해야 합니다.
@Primary,@Qualifier, 명시적 이름 조회 등으로 의도를 알려야 합니다.모든 클래스를 Bean으로 등록하면 좋은가요?
아닙니다. 협력 객체나 인프라 의존성처럼 컨테이너의 조립과 생명주기 관리가 필요한 대상을 중심으로 등록합니다. 값 객체나 메서드 안에서 잠깐 쓰는 객체까지 Bean으로 만들면 컨테이너 의존성과 구성 복잡도만 커질 수 있습니다.
프로그래밍 등록은 언제 필요한가요?
설정 데이터에 따라 여러 Bean을 반복해서 만들거나, 선언적 애너테이션만으로 표현하기 어려운 동적 등록 규칙이 있을 때 유용합니다. 일반적인 애플리케이션 Bean은 선언적 구성이 더 읽기 쉽습니다.
핵심 원리
- 일반 자바 객체와 Spring Bean의 차이는 컨테이너의 관리 대상인지에 있습니다.
- 컨테이너는 Bean Definition을 바탕으로 Bean을 생성하고 조립하고 관리합니다.
@Bean은 생성 과정을 직접 제어하고, 컴포넌트 스캔은 후보 클래스를 자동 탐색합니다.- 같은 타입의 Bean이 여러 개일 수 있으며 단일 주입에는 선택 기준이 필요합니다.
- 프로그래밍 등록은 동적인 등록 규칙이 있을 때 사용합니다.
출처
'Spring > 스프링 교과서' 카테고리의 다른 글
3 - 스프링 컨텍스트 빈 작성 (0) 2026.07.23 1 - 스프링 생태계 (0) 2026.07.13 - 구성 클래스에서