컴포넌트 스캔이란 Spring이 직접 클래스를 검색해서 Bean으로 등록해주는 기능으로, 자동 주입과 함께 사용된다. 이 기능을 통해 @Configuration 설정 클래스에서 따로 Bean으로 등록해두지 않아도 원하는 클래스를 Bean으로 등록할 수 있어서 설정 클래스의 코드를 크게 간소화할 수 있다.
@Component / @Component("값")
이 어노테이션이 사용된 클래스를 스캔 대상으로 지정한다. 스캔 대상으로 지정된 클래스는 Spring이 검색해서 Bean으로 등록할 수 있다.
이 어노테이션에 따로 값을 부여하지 않은 경우 클래스 이름의 첫 글자만 소문자로 바꾼 이름을 Bean 이름으로 사용한다. 만약 값을 부여했을 경우 그 값을 Bean 이름으로 사용하게 된다.
@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;
}
}
@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 = 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
서로 다른 타입에 동일한 Bean 이름을 사용한 상태에서 별도의 조치 없이 컴포넌트 스캔을 수행하면 ConflictingBeanDefinitionException이 발생한다.
따라서 타입이 서로 다른 두 Bean이 동일한 이름을 사용할 경우, 둘 중 하나에 명시적으로 Bean 이름을 지정해줘야 한다.
컴포넌트 스캔 대상인 클래스의 이름과 설정 클래스를 통해 수동 등록할 Bean의 이름이 같은 경우, 수동 등록하는 Bean을 우선적으로 취급한다. 따라서 이 경우에는 설정 클래스에서 정의된 Bean 1개만 존재한다.
만약 컴포넌트 스캔 대상인 클래스의 이름과 수동 등록할 Bean의 이름은 다르지만 타입은 동일한 경우, 각각의 Bean이 모두 존재할 수 있으므로 동일한 타입의 Bean이 여러 개가 된다.
따라서 자동 주입을 수행하는 코드에서는 @Qualifier를 사용하여 주입할 Bean을 정확하게 지정해야 Exception을 방지할 수 있다.