Spring

@ComponentScan 동작 원리에 대해서 설명해주세요

Spring이 클래스패스를 탐색하여 @Component 계열 어노테이션 클래스를 자동으로 Bean으로 등록하는 메커니즘. basePackages, includeFilters/excludeFilters, ASM 바이트코드 파싱의 내부 동작까지 인터랙티브 시각화로 완전 정복합니다.

2026년 3월 23일 · 약 13분 읽기

Q. “@ComponentScan의 동작 원리를 설명하고, basePackages와 includeFilters/excludeFilters 옵션이 어떻게 작동하는지 설명해주세요.”

예상 꼬리질문

답변 가이드

@ComponentScan은 Spring이 클래스패스를 탐색하여 @Component 계열 어노테이션이 붙은 클래스를 찾아 자동으로 Bean으로 등록하는 메커니즘입니다. 내부적으로 ClassPathBeanDefinitionScanner가 지정된 패키지를 탐색하고,CachingMetadataReaderFactory가 ASM 라이브러리로 .class 파일의 바이트코드를 파싱합니다. 이때 실제 클래스 로딩 없이 어노테이션 메타데이터만 읽어 불필요한 초기화를 방지합니다.

기본 스캔 범위는 @ComponentScan이 위치한 클래스의 패키지와 그 하위 패키지입니다. @SpringBootApplication에 @ComponentScan이 포함되어 있으므로, 메인 클래스를 최상위 패키지에 두어야 모든 하위 패키지가 스캔됩니다. 멀티 모듈 프로젝트나 형제 패키지에 컴포넌트가 있다면 basePackages로 명시해야 합니다.

includeFilters/excludeFilters로 스캔 대상을 정밀 제어할 수 있습니다. FilterType은 ANNOTATION, ASSIGNABLE_TYPE, ASPECTJ, REGEX, CUSTOM 5가지를 지원합니다. @Repository는 단순히 이름표가 아니라 JPA/JDBC 예외를 DataAccessException으로 자동 변환하는 중요한 부가기능이 있어, 계층별로 적절한 어노테이션을 선택하는 것이 중요합니다.

핵심 요약 (TL;DR)

  • - @ComponentScan은 클래스패스를 탐색해 @Component 계열 어노테이션 클래스를 자동 Bean 등록하는 메커니즘이다
  • - 기본 스캔 범위는 @ComponentScan/@SpringBootApplication이 위치한 패키지와 그 하위 패키지이다
  • - 내부 동작은 ASM 바이트코드 분석(클래스 로딩 없음) → includeFilters/excludeFilters 검사 → BeanDefinition 등록 순이다
  • - includeFilters/excludeFilters로 어노테이션·타입·정규식·AspectJ 패턴으로 스캔 대상을 정밀 제어할 수 있다
  • - @Repository는 JPA/JDBC 예외를 DataAccessException으로 자동 변환하는 부가기능이 있어 다른 계열과 다르다

Spring을 배울 때 “@Service를 붙이면 Bean이 된다”는 것은 알지만, 왜 되는지, 어떤 패키지까지 스캔되는지, 내부적으로 무슨 일이 일어나는지 설명하라고 하면 막히는 경우가 많습니다.

@ComponentScan은 Bean 챕터의 마지막 퍼즐 조각입니다. IoC 컨테이너가 Bean을 관리하고, Bean 등록 방법을 알았다면, 이제 Spring이 그 Bean을 어떻게 자동으로 발견하는지 이해할 차례입니다.


1. @SpringBootApplication 안에 무엇이 있나

꼬리질문: “@SpringBootApplication 안에 @ComponentScan이 포함된다고 했는데, 기본 스캔 범위는 어떻게 결정되나요?”

@SpringBootApplication은 사실 세 가지 어노테이션의 합성체입니다.@SpringBootConfiguration(설정), @EnableAutoConfiguration(자동 설정), @ComponentScan(컴포넌트 스캔)입니다.

기본 스캔 범위 결정 규칙: 별도 basePackages를 지정하지 않으면, @ComponentScan이 붙은 클래스가 위치한 패키지와 그 모든 하위 패키지를 스캔합니다. 메인 클래스가 com.example.myapp에 있으면 com.example.utils 같은 형제 패키지는 스캔되지 않습니다.

아래 데모에서 탭을 전환해 @SpringBootApplication 위치에 따른 스캔 범위 차이를 확인해보세요.

스캔 범위 데모

@SpringBootApplication 위치에 따라 스캔 범위가 어떻게 달라지는지 확인해보세요.

@SpringBootApplication이 최상위 패키지에 위치 → 모든 하위 패키지 스캔

📦com.example.myapp
├─MyApplication.java@SpringBootApplication
├─📦controller/
└─UserController.java@Controller✓ Bean 등록
├─📦service/
└─UserService.java@Service✓ Bean 등록
└─📦repository/
└─UserRepository.java@Repository✓ Bean 등록
스캔됨 (Bean 등록)
스캔 안됨
스캔 기준점

2. 내부 동작 — ASM으로 클래스를 읽는다

꼬리질문: “@ComponentScan은 내부적으로 어떻게 클래스패스를 탐색하나요? 실제로 클래스를 로딩하나요?”

많은 개발자가 오해하는 부분입니다. Spring은 컴포넌트 스캔 시 실제로 클래스를 로딩하지 않습니다. ASM 바이트코드 조작 라이브러리를 사용해 .class 파일에서 어노테이션 메타데이터만 읽습니다.

내부 핵심 클래스는 ClassPathBeanDefinitionScanner입니다. 이 클래스가 패키지를 classpath 리소스 패턴으로 변환하고,CachingMetadataReaderFactory로 각 .class 파일을 ASM 파싱합니다. 클래스 로딩 없이 어노테이션 메타데이터만 확인하므로 불필요한 static 초기화가 발생하지 않습니다.

아래 파이프라인 시각화에서 “자동 재생” 또는 “다음” 버튼으로 7단계를 직접 따라가보세요.

스캔 파이프라인 단계별 시각화

@ComponentScan이 클래스를 발견하고 Bean으로 등록하기까지 7단계를 확인해보세요.

1 / 7

패키지 → 리소스 패턴

basePackages 변환

ConfigurationClassParser

// basePackages → classpath 리소스 패턴으로 변환
"com.example.myapp"
  → "classpath*:com/example/myapp/**/*.class"

// 여러 패키지 지정 시
{ "com.example.service", "com.example.repository" }
  → "classpath*:com/example/service/**/*.class"
     "classpath*:com/example/repository/**/*.class"

@ComponentScan에 지정된 basePackages 문자열을 classpath 리소스 검색 패턴으로 변환합니다. 패키지 구분자(.)를 경로 구분자(/)로 바꾸고 /**/*.class 패턴을 추가합니다.


3. includeFilters / excludeFilters

꼬리질문: “includeFilters와 excludeFilters의 FilterType 종류와 사용 사례를 설명해주세요.”

필터를 통해 어떤 클래스를 스캔 대상에 포함하거나 제외할지 정밀하게 제어할 수 있습니다. FilterType은 ANNOTATION(어노테이션 기반), ASSIGNABLE_TYPE(타입 기반), ASPECTJ(AspectJ 표현식), REGEX(정규식), CUSTOM(TypeFilter 구현체) 5가지입니다.

useDefaultFilters = false로 설정하면 기본 필터(@Component 계열)가 비활성화됩니다. 이 경우 includeFilters에 명시한 조건만 스캔 대상이 됩니다. 특정 마커 어노테이션으로만 Bean을 제한할 때 유용합니다.

아래 시뮬레이터에서 필터를 조합하며 어떤 클래스가 Bean으로 등록되는지 확인해보세요.

필터 시뮬레이터

필터 조건을 변경하면 어떤 클래스가 스캔되는지 실시간으로 확인해보세요.

필터 설정

includeFilters

excludeFilters

5

/ 7개 클래스 Bean 등록

// 현재 설정

@ComponentScan(

useDefaultFilters = true,

)

클래스 스캔 결과

UserService.java@Service

service/

Bean 등록

@Service 어노테이션 → Bean 등록

OrderRepository.java@Repository

repository/

Bean 등록

@Repository 어노테이션 → Bean 등록

PaymentController.java@Controller

controller/

Bean 등록

@Controller 어노테이션 → Bean 등록

LegacyService.java@Deprecated@Service

service/

Bean 등록

@Service 어노테이션 → Bean 등록

AbstractBaseService.javaabstract@Service

service/

제외

abstract 클래스는 Bean 등록 불가

DataUtils.java@Component

utils/

Bean 등록

@Component 어노테이션 → Bean 등록

UserServiceTest.java

test/

스캔 안됨

@Component 계열 어노테이션 없음


4. @Component 계열 어노테이션의 차이

꼬리질문: “@Component, @Service, @Repository, @Controller의 차이는 무엇인가요? 모두 @Component와 동일한가요?”

Bean 등록 관점에서는 모두 동일합니다. @Service, @Repository, @Controller는 내부에 @Component를 포함하는 메타 어노테이션이라 동일하게 스캔됩니다.

그러나 @Repository는 중요한 부가기능이 있습니다. PersistenceExceptionTranslationPostProcessor가 @Repository Bean을 감지해 JPA EntityNotFoundException, Hibernate 예외 등 기술 종속적 예외를 Spring 표준 DataAccessException으로 자동 변환합니다. Service 계층이 JPA/JDBC 세부 구현에 의존하지 않도록 하는 중요한 추상화입니다.

아래 탐색기에서 각 어노테이션의 소스코드와 부가기능을 클릭해 확인해보세요.

@Component 계열 어노테이션 탐색기

어노테이션을 클릭해 계층 구조, 소스코드, 부가기능을 확인해보세요.

계층 구조

@Service 는 내부적으로 @Component 를 포함합니다.

Bean 등록 관점에서 @Component와 동일하게 동작합니다.

상세 정보

@Service

비즈니스 로직 계층

사용 시점: 비즈니스 로직, 트랜잭션 처리, 도메인 서비스

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component  // ← @Component를 포함!
public @interface Service {
  @AliasFor(annotation = Component.class)
  String value() default "";
}

5. Bean 이름 결정 규칙

꼬리질문: “컴포넌트 스캔에서 Bean 이름은 어떻게 결정되나요?”

AnnotationBeanNameGenerator가 Bean 이름을 결정합니다.

  • 1. 어노테이션 value 속성 명시 시 → 해당 이름 사용
  • 2. 미지정 시 → 클래스 단순명(Simple Name) 첫 글자 소문자 변환
  • 3. 예외: 두 번째 글자도 대문자면 그대로 유지 (URLShorteneruRLShortener)

이름 변환 예시

UserServiceuserService기본 규칙
@Service("myBean")myBean명시적 이름
URLShorteneruRLShortener2번째도 대문자 예외
ABCServiceaBCService첫 글자만 소문자

충돌 처리

Spring Boot 2.1+에서 같은 이름의 Bean이 두 곳에서 등록되면:

BeanDefinitionOverrideException

해결: @Service(“specificName”)으로 이름 명시 또는 spring.main.allow-bean-definition-overriding=true 설정


자주 발생하는 문제

실무에서 @ComponentScan 관련 자주 마주치는 오류와 해결 방법입니다.

NoSuchBeanDefinitionException — 스캔 범위 밖의 클래스

상황: No qualifying bean of type 'XxxService' available 에러

원인: 해당 클래스가 @ComponentScan 기준 패키지 하위에 없거나, @Component 계열 어노테이션이 누락됨

해결:

  1. basePackages 확장: @SpringBootApplication(scanBasePackages="com.example")
  2. @Bean 명시적 등록: @Configuration 클래스에서 직접 등록
  3. 어노테이션 추가: 클래스에 @Component/@Service 등 추가
BeanDefinitionOverrideException — 동일 이름 Bean 충돌

상황: 서로 다른 패키지에 같은 클래스명이 존재하거나, @Component와 @Bean으로 이중 등록

// com.example.a.UserService (@Service)
// com.example.b.UserService (@Service)
// → 동일 Bean 이름 "userService" 충돌!

해결:

  • - @Service("aUserService")로 이름 명시
  • - @Primary로 우선 순위 지정
  • - @Qualifier로 주입 시 명시적 지정
테스트에서 전체 컨텍스트가 로드되어 느린 경우

상황: 단위 테스트인데 @SpringBootTest로 전체 ApplicationContext가 로드되어 테스트가 느림

해결: 계층별 슬라이스 테스트 어노테이션 사용

// Controller 계층만 테스트
@WebMvcTest(UserController.class)

// Repository 계층만 테스트
@DataJpaTest

// Service 계층 단위 테스트
@ExtendWith(MockitoExtension.class)
// → @ComponentScan 없이 목 주입만

면접 체크리스트

이 항목들을 자신 있게 설명할 수 있다면 @ComponentScan 질문은 완벽하게 답할 수 있습니다.

  • - 기본 스캔 범위: @ComponentScan 위치 패키지 + 하위 패키지 (미지정 시)
  • - 내부 동작: ASM 바이트코드 파싱 (클래스 로딩 없음) → includeFilters/excludeFilters → BeanDefinition 등록
  • - FilterType 5가지: ANNOTATION, ASSIGNABLE_TYPE, ASPECTJ, REGEX, CUSTOM
  • - @Repository 부가기능: JPA/JDBC 예외 → DataAccessException 자동 변환 (다른 계열과 다름)
  • - Bean 이름 규칙: AnnotationBeanNameGenerator, 첫 글자 소문자 (2번째도 대문자면 예외)
  • - 충돌 처리: Spring Boot 2.1+에서 BeanDefinitionOverrideException, @Bean 이름 명시로 해결
  • - 더 알아볼 주제: @Import, @ImportResource, Auto-Configuration과의 차이

실전 퀴즈

시나리오 기반 문제로 @ComponentScan 이해도를 점검해 보세요.

1 / 5현재 점수: 0

케이스 1

스캔 범위 밖의 클래스, 어떻게 될까?

// 프로젝트 구조:
// com.example.myapp.Application.java (@SpringBootApplication)
// com.example.utils.StringUtils.java (@Component)

@Component
public class StringUtils {
  public String format(String s) { return s.trim(); }
}

// UserService에서 StringUtils를 주입하려고 합니다.
// 어떤 일이 발생할까요?

참고 자료


의견을 들려주세요

서비스 개선에 큰 도움이 됩니다. 익명으로 자유롭게 남겨주세요.

0 / 1000