Bean Overview

정의

Spring IoC 컨테이너는 하나 이상의 Bean을 관리한다. Bean은 컨테이너에 공급된 설정 메타데이터(어노테이션, Java 설정, XML <bean/> 정의 등)를 기반으로 생성된다.

컨테이너 내부에서 Bean 정의는 BeanDefinition 객체로 표현되며, 다음 메타데이터를 포함한다:

  • 패키지 수식 클래스명: Bean의 실제 구현 클래스
  • Bean 동작 설정: 스코프, 라이프사이클 콜백 등 컨테이너 내 동작 방식
  • 다른 Bean에 대한 참조: 협력 객체(collaborators) 또는 의존성(dependencies)
  • 기타 설정: 새로 생성되는 객체에 적용할 설정값 (예: 커넥션 풀 크기)

BeanDefinition 프로퍼티

프로퍼티설명
ClassBean 클래스 지정 (인스턴스화 방식 참조)
NameBean 네이밍 규칙
ScopeBean 스코프 (singleton, prototype 등)
Constructor arguments의존성 주입 (생성자 인자)
Properties의존성 주입 (프로퍼티 설정)
Autowiring mode자동 와이어링 모드
Lazy initialization mode지연 초기화 설정
Initialization method초기화 콜백 메서드
Destruction method소멸 콜백 메서드

외부에서 생성된 객체 등록

ApplicationContext 구현체는 컨테이너 외부에서 이미 생성된 객체의 등록도 허용한다. getAutowireCapableBeanFactory() 메서드로 DefaultListableBeanFactory에 접근하여 registerSingleton(..), registerBeanDefinition(..) 메서드를 사용한다.

Bean 메타데이터와 수동으로 공급된 싱글톤 인스턴스는 가능한 한 일찍 등록해야 한다. 컨테이너가 autowiring 및 기타 내부 검사(introspection) 단계에서 제대로 처리할 수 있도록 하기 위함이다. 런타임에 새 Bean을 등록하는 것은 공식적으로 지원되지 않으며, 동시 접근 예외컨테이너 상태 불일치를 초래할 수 있다.

Bean Overriding

Bean 오버라이딩은 이미 할당된 식별자로 Bean을 등록할 때 발생한다. 가능하지만 설정의 가독성을 떨어뜨린다.

Bean 오버라이딩은 향후 릴리스에서 deprecated 될 예정이다.


오버라이딩 제어

  • 완전 비활성화: ApplicationContext refresh 전에 allowBeanDefinitionOverriding 플래그를 false로 설정 → 오버라이딩 시 예외 발생
  • 기본 동작: 컨테이너가 오버라이딩 시도를 INFO 레벨로 로그에 기록
  • 로그 억제: allowBeanDefinitionOverridingtrue로 설정 (권장하지 않음)

Java Configuration에서의 오버라이딩

@Bean 메서드는 동일한 컴포넌트 이름을 가진 스캔된 Bean 클래스를 항상 묵시적으로 오버라이드한다 (반환 타입이 해당 Bean 클래스와 일치하는 한). 컨테이너는 Bean 클래스의 생성자 대신 @Bean 팩토리 메서드를 호출한다.

테스트 시나리오에서의 Bean 오버라이딩은 편의를 위해 명시적으로 지원된다.

Naming Beans

모든 Bean은 하나 이상의 식별자(identifier)를 가진다. 식별자는 컨테이너 내에서 고유해야 한다. 보통 하나의 식별자를 가지지만, 여러 개가 필요하면 추가 식별자는 앨리어스(alias)로 간주된다.


XML 기반 식별자 지정

  • id 속성: 정확히 하나의 식별자 지정
  • name 속성: 추가 앨리어스 지정 (쉼표,, 세미콜론;, 공백으로 구분)
  • idname을 모두 생략하면 컨테이너가 고유 이름을 자동 생성
  • 이름으로 참조가 필요한 경우(ref 요소, Service Locator 조회 등)에는 반드시 이름을 제공해야 한다

Bean 네이밍 컨벤션

Java 인스턴스 필드명의 표준 컨벤션을 따른다: 소문자로 시작하는 camelCase.

accountManager, accountService, userDao, loginController

일관된 네이밍은 설정의 가독성을 높이고, Spring AOP에서 이름 기반으로 관련 Bean에 어드바이스를 적용할 때도 유용하다.

컴포넌트 스캐닝 시 Spring은 이름 없는 컴포넌트에 대해 클래스의 단순 이름에서 첫 글자를 소문자로 변환하여 Bean 이름을 생성한다. 단, 첫 번째와 두 번째 문자가 모두 대문자인 경우에는 원래 대소문자를 유지한다. 이는 java.beans.Introspector.decapitalize와 동일한 규칙이다.


Bean 앨리어싱 (Aliasing)

Bean 정의 내에서 id 속성과 name 속성의 조합으로 여러 이름을 부여할 수 있다. 각 컴포넌트가 자신에게 특화된 Bean 이름으로 공통 의존성을 참조할 때 유용하다.

Bean이 정의된 곳에서 모든 앨리어스를 지정하는 것이 항상 적절하지는 않다. 대규모 시스템에서 설정이 서브시스템별로 분리된 경우, 다른 곳에서 정의된 Bean에 대한 앨리어스가 필요할 수 있다.

XML <alias/> 요소:

<alias name="fromName" alias="toName"/>

실전 예시 — 서브시스템 간 DataSource 공유:

<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

서브시스템 A는 subsystemA-dataSource, 서브시스템 B는 subsystemB-dataSource로 참조하지만, 실제로는 모두 myApp-dataSource라는 동일한 Bean을 가리킨다. 각 컴포넌트와 메인 애플리케이션이 고유한 이름으로 참조하면서도 충돌 없이 같은 객체를 공유한다.

Java Configuration에서는 @Bean 어노테이션으로 앨리어스를 제공할 수 있다.

Bean 인스턴스화 (Instantiating Beans)

Bean 정의는 본질적으로 하나 이상의 객체를 생성하기 위한 레시피다. 컨테이너는 요청 시 해당 레시피를 참조하여 실제 객체를 생성(또는 획득)한다.

XML 기반 설정에서 class 속성은 인스턴스화할 객체의 타입을 지정한다. 이 class 속성(BeanDefinitionClass 프로퍼티)은 대부분 필수이며, 두 가지 방식으로 사용한다:

  • 직접 생성: 컨테이너가 생성자를 리플렉션으로 호출 (Java의 new 연산자와 유사)
  • 정적 팩토리 메서드: 클래스의 static 팩토리 메서드를 호출하여 객체 생성

중첩 클래스 설정: 중첩 클래스의 바이너리 이름 또는 소스 이름을 사용할 수 있다. 예: com.example.SomeThing$OtherThing 또는 com.example.SomeThing.OtherThing


1. 생성자를 통한 인스턴스화

생성자 방식에서는 모든 일반 클래스가 Spring과 호환된다. 특정 인터페이스를 구현하거나 특정 방식으로 코딩할 필요가 없다. Bean 클래스를 지정하는 것만으로 충분하다. 단, 사용하는 IoC 유형에 따라 기본(빈) 생성자가 필요할 수 있다.

Spring IoC 컨테이너는 사실상 어떤 클래스든 관리할 수 있다. 순수 JavaBean뿐 아니라 JavaBean 명세를 따르지 않는 레거시 커넥션 풀 같은 클래스도 관리 가능하다.

<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

생성자 인자의 경우, 컨테이너가 여러 오버로드된 생성자 중 적절한 것을 선택할 수 있다. 모호함을 피하기 위해 생성자 시그니처를 가능한 단순하게 유지하는 것을 권장한다.


2. 정적 팩토리 메서드를 통한 인스턴스화

class 속성에 static 팩토리 메서드를 포함하는 클래스를 지정하고, factory-method 속성에 팩토리 메서드명을 지정한다. 이 메서드를 호출하여 반환된 객체는 생성자로 생성된 것처럼 취급된다. 레거시 코드의 static 팩토리를 호출하는 데 활용할 수 있다.

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

팩토리 메서드 오버로딩 시에도 모호함을 피하기 위해 시그니처를 단순하게 유지하는 것을 권장한다.


3. 인스턴스 팩토리 메서드를 통한 인스턴스화

컨테이너에 있는 기존 Bean의 비정적 메서드를 호출하여 새 Bean을 생성한다. class 속성은 비워두고, factory-bean 속성에 팩토리 Bean을, factory-method 속성에 팩토리 메서드를 지정한다.

<!-- 팩토리 Bean -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator"/>

<!-- 팩토리 Bean을 통해 생성할 Bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {
    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

하나의 팩토리 클래스가 여러 팩토리 메서드를 가질 수도 있다:

<bean id="serviceLocator" class="examples.DefaultServiceLocator"/>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {
    private static ClientService clientService = new ClientServiceImpl();
    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

팩토리 Bean 자체도 DI를 통해 관리·설정될 수 있다.

Spring 문서에서 “factory bean” 은 컨테이너에서 인스턴스/정적 팩토리 메서드를 통해 객체를 생성하도록 설정된 Bean을 가리킨다. 반면 FactoryBean (대문자 주의)은 Spring 고유의 FactoryBean 인터페이스 구현 클래스를 가리킨다.


Bean의 런타임 타입 결정

특정 Bean의 런타임 타입을 결정하는 것은 간단하지 않다. Bean 메타데이터의 class는 초기 클래스 참조일 뿐이며, 다음 요인들로 인해 실제 타입이 달라질 수 있다:

  • 선언된 팩토리 메서드와의 조합
  • FactoryBean 클래스인 경우 다른 런타임 타입 가능
  • 인스턴스 팩토리 메서드의 경우 class가 아예 설정되지 않음
  • AOP 프록시가 Bean 인스턴스를 인터페이스 기반 프록시로 감쌀 수 있음

실제 런타임 타입을 확인하는 권장 방법은 BeanFactory.getType(beanName) 호출이다. 이 메서드는 위의 모든 경우를 고려하여 BeanFactory.getBean이 반환할 객체의 타입을 알려준다.

관련 문서