🐢 꼬부기 LV.1 | 개념•기초/💧물대포(핵심개념)

Spring Bean 등록 방법 4가지

서화 2026. 3. 25. 16:41

스프링 빈을 등록하는 방법은 4가지가 있다

스프링 버전에 따라 다르긴한데 XML(1.x) → @Component(2.5) → @Bean(3.0) → 함수형(5.0)으로 등록할수 있다

여기서 가장 중요한 구분기준은 내가 수정할수있는 소스코드를 가진 클래스인가를 확인해야한다

내 클래스라면 @Component를 사용하고 외부 라이브러리라면 @Bean을 쓰는 게 원칙이다

4가지 방법 한눈에 보기

방법 클래스 구분/ 타입 실무사용여부 장점 단점
@Component 계열 내가 작성한 클래스/
타입 안전
매우 많이사용함 코드가 간결함
역할이 명확함
레포지토리 예외 변환 부가기능 있음
외부 라이브러리 클래스에 못씀
조건부 등록 표현이 어려움
@Bean 모든 클래스/타입 안전
외부라이브러리 사용
많이 사용함 외부라이브러리 등록가능
복잡한 초기화 로직 자유롭게 작성가능
타입 안정성 보장
@Component 보다 코드량이 많음
반드시 @Configuration 안에 정의해야 싱글톤 보장
XML설정 모든클래스/타입 불안전
외부라이브러리 사용
레거시 고칠때 사용 코드 변경없이도 설정 수정가능
비개발자도 설정파일 수정 가능
타입 안전성이 없음
오타는 런타임에 발견가능
IDE 지원약함
코드가 너무 김
함수형등록 모든클래스/타입 안전
외부라이브러리 사용
거의 사용안함 빠른 시작시간
람다로 타입 안전성 보장
리플렉션없음

코드가 너무 길고 불편함
AOP 통합이 복잡함
일반 프로젝트에서 사용하기 과하다

리플렉션 : 런타임(프로그램 실행 중)에 클래스, 메서드, 필드 등 객체의 메타데이터를 분석하고, 동적으로 조작(생성, 호출)할 수 있는 자바 API 및 프로그래밍 기법

자세히 알아보기

@Component 스캔 동작원리

@Service
public class UserService {
  private final UserRepository repo;

  public UserService(UserRepository repo) {
    this.repo = repo;
  }
}

@SpringBootApplication 안에 @ComponentScan이 포함되있어 메인클래스가 있는 패키지 아래에 있는 클래스를 자동으로 스캔한다 따라서 @Component,@Service,@Repository,@Controller어노테이션이 붙은 클래스를 스캔해서 Bean으로 등록한다

@Service,@Repository,@Controller는 @Component안에 속해 있으며 @Repository는 데이터 접근 예외상황을 스프링이 일관되게 정의한 DataAccessException으로 자동변환이 가능하다

@Service -> 비즈니스 로직 계층

@Repository-> 데이터 접근 계층

@Controller -> 웹 MVC 컨트롤러, 디스패쳐 서블릿이 요청을 위임함

NoSuchBeanDefinitionException

클래스가 스캔 범위 밖에 있을때 발생 따라서 해당 클래스를 사용하고 싶다면 스캔범위를 지정해줘야함

@SpringBootApplication(scanBasePackages = ...)

@Bean - 외부라이브러리와 CGLIB 프록시

CGLIB(Code Generation Library) 프록시 : 인터페이스 없이 구체 클래스를 상속받아 런타임에 바이트코드를 조작하여 동적으로 프록시 객체를 생성하는 기술

@Configuration 클래스 안에서 @Bean 어노테이션 메서드를 정의하면 등록이 가능하다 소스 코드를 수정할수 없는 외부 클래스에 사용할 수 있다

 핵심은 CGLIB 프록시 사용이다 따라서 @Bean 메서드를 직접 호출해도 항상 같은 싱글톤의 인스턴스가 반환된다

여기서 주의할점은 @Configuration아니라 @Component를 사용하면 매번 새 인스턴스가 생기기때문에 주의해야한다

@Configuration  // CGLIB 프록시 O
public class AppConfig {

  @Bean
  public A beanA() {
    return new A(beanB()); // ①
  }

  @Bean
  public B beanB() {
    return new B();
  }

  @Bean
  public C beanC() {
    return new C(beanB()); // ②
  }
}

결과

beanA() 호출됨
beanB() 첫 번째 호출 ID: #001
beanC() 호출됨
beanB() 두 번째 호출 ID: #001

매번같은 싱글톤 보장됨

@Component(lite mode)  // CGLIB 프록시 X
public class AppConfig {

  @Bean
  public A beanA() {
    return new A(beanB()); // ①
  }

  @Bean
  public B beanB() {
    return new B();
  }

  @Bean
  public C beanC() {
    return new C(beanB()); // ②
  }
}

결과

beanA() 호출됨
beanB() 첫 번째 호출 ID: #001
beanC() 호출됨
beanB() 두 번째 호출 ID: #002

싱글톤 보장 안됨 -> 매번 새 인스턴스 생김

실무 선택 기준

대부분 Spring Boot 프로젝트는 @Component 계열과 @Bean 두가지 만으로 충분하다

XML은 레거시 프로젝트 유지보수에만 사용하고 함수형 등록은 서버리스·네이티브 이미지 환경이 아니면 사용하기 과하다

자주발생하는 문제 모음

@Configuration Lite Mode

싱글톤이 깨지는 경우

상황 : 설정 클래스에 @Configuration 대신 @Component를 사용했을때, @Bean 메서드 간 호출에서 매번 새 인스턴스가 생성된다

원인: @Component를 사용하면 CGLIB 프록시가 적용되지 않아(lite mode), @Bean 메서드가 일반 메서드처럼 호출된다

해결: Bean 간 의존 관계가 있는 설정 클래스는 반드시 @Configuration을 사용한다

BeanDefinitionOverrideException

Bean 이름 충돌

상황: 동일한 이름의 Bean이 두 곳에서 등록될 때 Spring Boot 2.1+에서 예외가 발생한다.

원인: 같은 클래스명을 가진 컴포넌트가 여러 패키지에 존재하거나, @Component와 @Bean으로 이중 등록됐다.

해결: @Bean('primaryDataSource')처럼 Bean 이름을 명시적으로 지정하거나, 중복 등록 원인을 찾아 제거한다.