yja

7. 컴포넌트 스캔 본문

[Web-Back] Spring/스프링 핵심 원리 - 기본편 (강의)

7. 컴포넌트 스캔

유진진 2026. 4. 6. 21:47

컴포넌트 스캔과 의존관계 자동 주입 

1. 컴포넌트 스캔 

  • `@ComponentScan`
    • : `@Component`가 붙은 클래스를 스캔해서 스프링 빈으로 등록해준다. 

@Configuration
@ComponentScan( // @Component가 붙은 클래스를 스프링 빈으로 등록한다.
        // @Configuration안에 @Component가 있기 때문에 스캔되지 않도록 제외시킨다.
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}

 

 

2. 의존 관계 주입 

`@Component` 를 붙인다. 이러면 스프링 빈으로 자동으로 등록되는데 이때 의존관계를 어떻게 주입해줘야할까?

자동으로 의존관계를 주입해주기 위해 생성자에 `@Autowired`를 붙여야한다. 

  • `@Autowired`
    • : 생성자에서 의존성을 자동으로 주입해준다.
    • `ac.getBean(MemoryRepository.class)` 역할을 해준다. 
    • 기본 조회 전략: 타입이 같은 빈을 찾아서 주입한다.

@Component
public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

    @Autowired
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

 

 

 

 

탐색 위치와 기본 스캔 대상 

1. 컴포넌트 탐색 위치 

  • `basePackages`: 탐색할 패키지를 지정하여 탐색 
  • `basePackageClasses`: 클래스가 속한 패키지를 기준으로 탐색 

지정하지 않으면 `@ComponentScan`이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다. 

권장방식은 지정하기 않고 설정 정보 클래스를 프로젝트 최상단에 두는 것이다. -> 프로젝트 전체를 스캔한다. 

@ComponentScan( 
        basePackages = "hello.core.member",
        basePackageClasses = AutoAppConfig.class
)

 

 

2. 컴포넌트 스캔의 기본 대상 

  • `@Component` : 컴포넌트 스캔에서 사용
  • `@Controller` : 스프링 MVC 컨트롤러에서 사용
  • `@Service` : 스프링 비즈니스 로직에서 사용
  • `@Repository` : 스프링 데이터 접근 계층에서 사용
  • `@Configuration` : 스프링 설정 정보에서 사용

이 컴포넌트들 안에 `@Component` 가 있어서 스프링 빈으로 등록되고 스캔의 대상이 된다. 

 

 

 

 

 

필터

  • `includeFilters` : 컴포넌트 스캔 대상을 추가로 지정한다.
  • `excludeFilters` : 컴포넌트 스캔에서 제외할 대상을 지정한다.

1. 어노테이션 만들기 

// 컴포넌트 스캔 대상에 추가할 애노테이션
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
// 컴포넌트 스캔 대상에서 제외할 애노테이션
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}

 

2. 클래스에 방금 만든 어노테이션 추가 

@MyIncludeComponent
public class BeanA {
}
@MyExcludeComponent
public class BeanB {
}

 

3. 설정 정보 클래스 

여기서 @ComponentScan에 `includeFilters`와 `excludeFilters` 를 통해 컴포넌트 스캔의 범위를 지정한다. 

@Configuration
@ComponentScan(
        includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
static class ComponentFilterAppConfig {
}

 

4. 테스트

BeanA는 컴포넌트 스캔 대상이므로 스프링 빈으로 등록되고, BeanB는 컴포넌트 스캔 대상에서 제외되었으므로 스프링 빈으로 등록되지 않는다. 

@Test
void filterScan() {
    ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
    BeanA beanA = ac.getBean("beanA", BeanA.class);
    assertThat(beanA).isNotNull();

//  ❌ ac.getBean("beanB", BeanB.class); // BeanB는 빈 등록 제외함 // 빈이 없으므로 오류 발생
    org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class,
            () -> ac.getBean("beanB", BeanB.class));
}

 

 

 

 

중복 등록과 충돌 

컴포넌트 스캔에서 같은 빈 이름을 등록하면 무조건 충돌될까?

 

1. 자동 빈 등록 vs 자동 빈 등록

다른 두 스프링 빈을 같은 이름으로 만들어보면, `BeanDefinitionStoreException` 가 나온다.  

@Component("service")
public class OrderServiceImpl implements OrderService{}

@Component("service")
public class MemberServiceImpl implements MemberService{

 

 

2. 자동 빈 등록 vs 수동 빈 등록

// 자동 빈 등록 
@Component
public class MemoryMemberRepository implements MemberRepository{}
@Configuration
@ComponentScan()
public class AutoAppConfig {

	// 수동 빈 등록 
    @Bean("memoryMemberRepository")
    MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

}

 

이 경우 관련 테스트 코드를 실행하면 오류가 발생하지 않는다. 

수동 등록이 우선권을 갖게 된다. (오버라이딩)

 

하지만 이걸 예상하고 일부러 개발자가 같은 이름으로 빈을 등록하는 경우는 거의 없다.

그래서 최신 스프링에서는 스프링 부트인 최상단 Application 코드를 실행하면 오류가 발생하도록 한다.