Chapter 05 - 컴포넌트 스캔

이현빈·2024년 3월 18일

1. 컴포넌트 스캔이란?

@Component: 스캔 대상 지정하기

컴포넌트 스캔이란 Spring이 직접 클래스를 검색해서 Bean으로 등록해주는 기능으로, 자동 주입과 함께 사용된다. 이 기능을 통해 @Configuration 설정 클래스에서 따로 Bean으로 등록해두지 않아도 원하는 클래스를 Bean으로 등록할 수 있어서 설정 클래스의 코드를 크게 간소화할 수 있다.

  • @Component / @Component("값")
    이 어노테이션이 사용된 클래스를 스캔 대상으로 지정한다. 스캔 대상으로 지정된 클래스는 Spring이 검색해서 Bean으로 등록할 수 있다.
    이 어노테이션에 따로 값을 부여하지 않은 경우 클래스 이름의 첫 글자만 소문자로 바꾼 이름을 Bean 이름으로 사용한다. 만약 값을 부여했을 경우 그 값을 Bean 이름으로 사용하게 된다.

@ComponentScan: 스캔 설정하기

  • @ComponentScan(basePackages = {"패키지명"})
    이 어노테이션은 @Configuration 설정 클래스에 적용하여야 사용할 수 있다.
    이 어노테이션이 붙은 설정 클래스는 지정된 패키지와 그 하위 패키지에 속한 클래스 중 @Component가 붙은 클래스의 객체를 생성하여 Bean으로 등록한다. 만약 스캔하고자 하는 패키지가 여러 개일 경우 basePackage 속성에 배열을 사용한다.
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import spring.MemberPrinter;
import spring.MemberSummaryPrinter;
import spring.VersionPrinter;

/* spring 패키지와 그 하위 패키지를 대상으로 컴포넌트 스캔 수행 */
@Configuration
@ComponentScan(basePackages = {"spring"})
public class AppCtx {
	@Bean
	@Qualifier("printer")
	public MemberPrinter memberPrinter1() {
		return new MemberPrinter();
	}

	@Bean
	@Qualifier("summaryPrinter")
	public MemberSummaryPrinter memberPrinter2() {
		return new MemberSummaryPrinter();
	}
	
	@Bean
	public VersionPrinter versionPrinter() {
		VersionPrinter versionPrinter = new VersionPrinter();
		versionPrinter.setMajorVersion(5);
		versionPrinter.setMinorVersion(0);
		return versionPrinter;
	}
}

2. 스캔 대상에서 제외 or 포함하기

스캔 대상 제외

@ComponentScan에서 excludeFilter 속성과 @Filter 어노테이션을 사용하여 스캔 대상에서 제외할 클래스를 별도로 지정한다. 만약 여러 개의 필터를 설정할 경우 excludeFilter 속성에 배열을 사용한다.

 @ComponentScan(basePackages = {"패키지명1", "패키지명2", ...}, 
 	excludeFilters = {
    	@Filter(type = 필터링 방식1, pattern = "제외 대상1")
        @Filter(type = 필터링 방식2, classes = "제외 대상2")
        ...
})

스캔 대상 포함

위의 스캔 대상 제외와 동일한 방식으로 스캔 대상을 포함할 수 있다. 단, @ComponentScan에서 includeFilter 속성을 사용한다는 차이가 있다.

@ComponentScan(basePackages = {"패키지명1", "패키지명2", ...}, 
 	includeFilters = {
    	@Filter(type = 필터링 방식1, pattern = "포함 대상1")
        @Filter(type = 필터링 방식2, classes = "포함 대상2")
        ...
})

@Filter의 type 속성을 통한 스캔 대상 필터링

  • @Filter(type = FilterType.REGEX, pattern = "포함/제외 대상")
    : 정규표현식으로 스캔 대상을 필터링한다.
  • @Filter(type = FilterType.ASPECTJ, pattern = "포함/제외 대상")
    : AspectJ 패턴으로 스캔 대상을 필터링한다.
  • @Filter(type = FilterType.ANNOTATION, classes = "포함/제외 대상")
    : 특정 어노테이션의 사용 여부를 통해 컴포넌트 스캔 대상을 필터링한다.
  • @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = "포함/제외 대상")
    : 특정 타입이나 그 하위 타입을 통해 컴포넌트 스캔 대상을 필터링한다.

기본 스캔 대상

@Component이 붙은 클래스뿐만 아니라 아래의 어노테이션이 사용된 클래스 역시 컴포넌트 스캔 대상에 포함된다. 이 중 @Aspect를 제외한 나머지는 모두 @Component 어노테이션에 관한 특수 어노테이션이다.

  • @Conponent
  • @Controller
  • @Service
  • @Repository
  • @Configuration
  • @Aspect

3. 컴포넌트 스캔에서의 충돌 처리

Bean 이름이 충돌하는 경우

서로 다른 타입에 동일한 Bean 이름을 사용한 상태에서 별도의 조치 없이 컴포넌트 스캔을 수행하면 ConflictingBeanDefinitionException이 발생한다.
따라서 타입이 서로 다른 두 Bean이 동일한 이름을 사용할 경우, 둘 중 하나에 명시적으로 Bean 이름을 지정해줘야 한다.

수동 등록한 Bean과 충돌하는 경우

컴포넌트 스캔 대상인 클래스의 이름과 설정 클래스를 통해 수동 등록할 Bean의 이름이 같은 경우, 수동 등록하는 Bean을 우선적으로 취급한다. 따라서 이 경우에는 설정 클래스에서 정의된 Bean 1개만 존재한다.

만약 컴포넌트 스캔 대상인 클래스의 이름과 수동 등록할 Bean의 이름은 다르지만 타입은 동일한 경우, 각각의 Bean이 모두 존재할 수 있으므로 동일한 타입의 Bean이 여러 개가 된다.
따라서 자동 주입을 수행하는 코드에서는 @Qualifier를 사용하여 주입할 Bean을 정확하게 지정해야 Exception을 방지할 수 있다.


Reference

  • 초보 웹 개발자를 위한 스프링 5 프로그래밍 입문(최범균)

0개의 댓글