컴포넌트 스캔은 스프링이 직접 클래스를 검색해서 빈으로 등록하는 기능이다. 이 기능을 사용하면 설정 클래스를 사용하지 않아도 되므로 설정 코드가 줄어든다. @Componenet 어노테이션을 클래스에 사용해 해당 클래스를 스캔 대상으로 설정할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// @Component 어노테이션만 사용하면
// 클래스 이름에서 첫 글자를 소문자로 바꾼 이름이
// 빈 이름으로 사용된다.
@Component
public class Bean {
public void printHello() {
System.out.println("hello");
}
}
// 속성 값을 주면 해당 속성 값이 빈 이름으로 사용된다.
@Component("bean1")
public class Bean {
public void printHello() {
System.out.println("hello");
}
}
|
cs |
@ComponentScan 어노테이션으로 스캔 설정
@Compoenent 어노테이션을 붙인 클래스를 스캔해 스프링 빈으로 등록하기 위해서는 @ComponentScan 어노테이션을 사용해야 한다.
1
2
3
|
// setter 라는 패키지와 그 하위 패키지에 속한 클래스를 스캔 대상으로 설정한다.
// 스캔을 하면서 @Component가 붙은 클래스의 객체를 생성해 빈으로 등록한다.
@ComponentScan(basePackages = {"setter"})
|
cs |
만약 스캔시 특정 대상을 자동 등록 대상에서 제외하고 싶다면 @ComponentScan 어노테이션의 excludeFilters 속성을 사용하면 된다.
excludeFilters 속성은 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
...
/**
* Specifies which types are not eligible for component scanning.
* @see #resourcePattern
*/
Filter[] excludeFilters() default {};
...
}
|
cs |
excludeFilters 속성은 다음과 같이 사용할 수 있다.
1
2
3
4
5
6
|
// setter 패키지 부터 스캔을 한다.
// filter 방식을 정규식으로 한다.
// "spring." 으로 시작하고 Dao로 끝나는 컴포넌트를 스캔 대상에서 제외한다.
@ComponentScan(basePackages = {"setter"},
excludeFilters = @Filter(type = FilterType.REGEX, pattern="spring\\..*Dao"))
|
cs |
FilterType은 다음과 같은 종류가 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public enum FilterType {
/**
* Filter candidates marked with a given annotation.
* @see org.springframework.core.type.filter.AnnotationTypeFilter
*/
ANNOTATION,
/**
* Filter candidates assignable to a given type.
* @see org.springframework.core.type.filter.AssignableTypeFilter
*/
ASSIGNABLE_TYPE,
/**
* Filter candidates matching a given AspectJ type pattern expression.
* @see org.springframework.core.type.filter.AspectJTypeFilter
*/
ASPECTJ,
/**
* Filter candidates matching a given regex pattern.
* @see org.springframework.core.type.filter.RegexPatternTypeFilter
*/
REGEX,
/** Filter candidates using a given custom
* {@link org.springframework.core.type.filter.TypeFilter} implementation.
*/
CUSTOM
}
|
cs |
이 중 AspectJ 패턴을 사용하기 위해서는 aspectjweaver 모듈을 추가해야 한다.
1
|
implementation 'org.aspectj:aspectjweaver:1.9.9.1'
|
cs |
스프링 부트에서 기본으로 사용되는, @SpringBootApplication 내부에 존재하는 @ComponentScan은 다음과 같은 속성을 가지고 있다.
1
2
|
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
|
cs |
기본 스캔 대상
@Component 어노테이션 뿐만 아니라 다음 어노테이션들 역시 컴포넌트 스캔 대상에 포함된다.
1. @Compoenent (org.springframework.stereotype)
2. @Controller (org.springframework.stereotype)
3. @Service (org.springframework.stereotype)
4. @Repository (org.springframework.stereotype)
5. @Aspect (org.aspectj.lang.annotation)
6. @Configuration (org.springframework.context.annotation)
이 어노테이션들 중 @Controller, @Service, @Repository는 내부적으로 @Component 어노테이션을 사용한다. @Aspect에 대한 내용은 추후 AOP에서 다루자.
컴포넌트 스캔에 따른 충돌 처리
컴포넌트 스캔 기능을 사용할 때 빈 이름 충돌과 수동 등록에 따른 충돌을 조심해야 한다.
빈 이름 충돌
빈 이름 충돌은 빈 스캔 대상이 되는 두 개 이상의 클래스가 같은 이름을 가질 때 발생한다. 예를 들어 conflict 패키지 내부에 Conflict 클래스가 존재하고 conflict2 패키지에도 Conclict 클래스가 존재한다 해보자. 이 두 클래스 모두 @Component로 빈 스캔 대상으로 등록하면 다음과 같은 에러가 발생한다.
1
2
3
4
5
|
org.springframework.beans.factory.BeanDefinitionStoreException:
Failed to parse configuration class [setter.Main]; nested exception
is org.springframework.context.annotation.ConflictingBeanDefinitionException:
Annotation-specified bean name 'conflict' for bean class [setter.conflict.Conflict]
conflicts with existing, non-compatible bean definition of same name and class [setter.Conflict2.Conflict]
|
cs |
이 경우 @Component에 속성값을 부여해 둘 중 하나를 명시적으로 빈 이름을 지정해야 하낟.
수동 등록한 빈과 충돌
만약 다음과 같이 자동 등록 빈과 수동 등록 빈의 이름이 같다면 수동 등록한 빈이 우선권을 갖는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 수동 등록
@Configuration
public class Config {
@Bean
public setter.Bean bean() {
return new setter.Bean();
}
}
// 자동 등록
// @Component에 속성으로 이름을 명시하지 않으면
// 클래스 이름에서 첫 글자가 소문자로 바뀐 문자열이
// 빈의 이름이 된다.
@Component
public class Bean {
public void printHello() {
System.out.println("hello");
}
}
|
cs |
만약 여기서 수동 등록 빈의 이름을 다음과 같이 수정한다면.
1
2
3
4
|
@Bean
public setter.Bean bean2() {
return new setter.Bean();
}
|
cs |
수동 등록 빈의 이름과 자동 등록 빈의 이름이 다르므로 같은 Bean 타입의 빈이 두 개가 생성된다. 이 때는 @Qualifier 어노테이션을 사용해 알맞은 빈을 선택해야 한다.
출처 - 초보 웹 개발자를 위한 스프링 5 프로그래밍 입문