컴포넌트 스캔은 스프링이 직접 클래스를 검색해서 빈으로 등록하는 기능이다. 이 기능을 사용하면 설정 클래스를 사용하지 않아도 되므로 설정 코드가 줄어든다. @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 프로그래밍 입문
'도서 > 초보 웹 개발자를 위한 스프링 5 프로그래밍 입문' 카테고리의 다른 글
| Chapter10. 스프링 MVC 프레임워크 동작 방식 (0) | 2022.08.21 | 
|---|---|
| Chapter 8. DB 연동 (0) | 2022.05.24 | 
| Chapter 6. 빈 라이프사이클 범위 (0) | 2022.05.05 | 
| Chapter 4. 의존 자동 주입 (0) | 2022.05.02 | 
| Chapter 03. Spring DI(Dependency Injection) (0) | 2022.04.25 |