4 분 소요

자동 주입과 함께 사용하는 추가 기능이 컴포넌트 스캔이다. 컴포넌트 스캔은 스프링이 직접 클래스를 검색해서 빈으로 등록해주는 기능이다. 설정 클래스에 빈으로 등록하지 않아도 원하는 클래스를 빈으로 등록할 수 있으므로 컴포넌트 스캔 기능을 사용하면 설정 코드가 클게 줄어든다.

1. @Component 애노테이션으로 스캔 대상 지정

@Component 애노테이션은 해당 클래스를 스캔 대상으로 표시한다.
값을 주었는지에 따라 빈으로 등록할 때 사용할 이름이 결정된다.

  • @Component 애노테이션에 값을 주지 않을 경우
    클래스 이름의 첫 글자를 소문자로 바꾼 이름을 빈 이름으로 사용한다.
    예를 들어 클래스 이름이 MemberDao이면 빈 이름으로 “memberDao”를 사용한다.
  • @Component 애노테이션에 값을 주는 경우 그 값을 빈 이름으로 사용한다.
1
2
3
4
@Component("listPrinter")
public class MemberListPrinter{
    ...생략
}

2. @ComponentScan 애노테이션으로 스캔 설정

@Component 애노테이션을 붙인 클래스를 스캔해서 스프링 빈으로 등록하려면 설정 클래스에 @ComponentScan 애노테이션을 적용해야 한다.
설정 클래스인 Appctx에 @ComponentScan 애노테이션을 적용한 코드는 아래와 같다

1
2
3
4
5
6
7
8
9
10
11
import org.springframework.context.annotation.ComponentScan;

@Configuration
@ComponentScan(basePackages = {"spring"})
public class AppCtx {
	@Bean
	public MemberPrinter memberPrinter() {
		return new MemberPrinter();
	}
	
}
  • 스프링 컨테이너가 @Component 애노테이션을 붙인 클래스를 검색해서 빈으로 등록해주기 때문에 설정 코드가 줄어든 것을 알 수 있다.
  • @ComponentScan 애노테이션의 basePackages 속성값은 {“spring”}이다. 이 속성은 스캔 대상 패키지 목록을 지정한다.
  • 이는 spring 패키지와 그 하위 패키지에 속한 클래스를 스캔 대상으로 설정한다.
  • @Component 애노테이션이 붙은 클래스의 객체를 생성해서 빈으로 등록한다.

3. 스캔 대상에서 제외하거나 포함하기

excludeFilters 속성을 사용하면 스캔할 때 특정 대상을 자동 등록 대상에서 제외할 수 있다. 다음 코드는 excludeFilters 속성의 사용 예를 보여준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(basePackages = {"spring"},
    excludeFilters = @Filter(type = FilterType.REGEX, pattern = "spring\\..*Dao"))
public class AppCtx {
	@Bean
	public MemberPrinter memberPrinter() {
		return new MemberPrinter();
	}
	
}

3.1 FilterType.REGEX

이 코드는 @Filter 애노테이션의 type 속성값으로 FilterType.REGEX를 주었다. 이는 정규표현식을 사용해서 제외 대상을 지정한다는 것을 의미한다.
pattern 속성은 FilterType에 적용할 값을 설정한다. 위 설정에서 “Spring.”으로 시작하고 Dao로 끝나는 정규표현식을 지정했으므로 spring.MemberDao 클래스를 컴포넌트 스캔 대상에서 제외한다.

3.2 FilterType.ASPECTJ

filterType.ASPECTJ를 필터 타입으로 설정할 수도 있다. 이 타입을 사용하면 정규표현 대신 AspectJ 패턴을 사용해서 대상을 지정한다.

1
2
3
4
5
6
7
8
9
10
@Configuration
@ComponentScan(basePackages = {"spring"},
    excludeFilters = @Filter(type = FilterType.ASPECTJ, pattern = "spring.*Dao"))
public class AppCtx {
	@Bean
	public MemberPrinter memberPrinter() {
		return new MemberPrinter();
	}
	
}

AspectJ 패턴이 동작하려면 의존 대상에 aspectjweaver 모듈을 추가해야 한다.

  • patterns 속성
    String[] 타입이므로 배열을 이용해서 패턴을 한 개 이상 지정할 수 있다.

3.3 FilterType.ANNOTATION

특정 애노테이션을 붙인 타입을 컴포넌트 대상에서 제외할 수도 있다. 예를 들어 다음의 @NoProduct나 @ManualBean 애노테이션을 붙인 클래스는 컴포넌트 스캔 대상에서 제외하고 싶다고 하자.

1
2
3
4
5
6
7
8
9
@Retention(RUNTIME)
@Target(TYPE)
public @interface NoProduct{
}

@Retention(RUNTIME)
@Target(TYPE)
public @interface ManualBean{
}

이 두 애노테이션을 붙인 클래스를 컴포넌트 스캔 대상에서 제외하려면 다음과 같이 excludeFilters 속성을 설정한다.

1
2
3
4
5
6
@Configuration
@ComponentScan(basePackages = {"spring", "spring2"},
    excludeFilters = @Filter(type = FilterType.ANNOTATION,
                            classes = {NoProduct.class, ManualBean.class}))
public class AppCtx{
}

type 속성값으로 FilterType.ANNOTATION을 사용하면 classes 속성에 필터로 사용할 애노테이션 타입을 값으로 준다. 이 코드는 @ManualBean 애노테이션을 제외 대상에 추가했으므로 다음 클래스를 컴포넌트 스캔 대상에서 제외한다.

1
2
3
4
5
@ManualBean
@Bean
public class MemberDao{
    ...
}

3.4 FilterType.ASSIGNABLE_TYPE

특정 타입이나 그 하위 타입을 컴포넌트 스캔 대상에서 제외하려면 ASSIGNABLE_TYPE을 사용한다.

1
2
3
4
5
6
@Configuration
@ComponentScan(basePackages = {"spring"},
    excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE,
                            classes = MemberDao.class))
public class AppCtx{
}
  • classes 속성에는 제외할 타입 목록을 지정한다.

설정한 필터가 두 개 이상이면 @ComponentScan의 excludesFilters 속성에 배열을 사용해서 @Filter 목록을 전달한다. 다음은 예이다.

1
2
3
4
5
6
7
8
@Configuration
@ComponentScan(basePackages = {"spring"},
    excludesFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = ManualBean.class),
        @Filter(type = FilterType.REGX, pattern = "spring2\\..*")
})
public class AppCtx{
}

3.5 기본 스캔 대상

@Component 애노테이션을 붙인 클래스만 컴포넌트 스캔 대상에 포함되는 것은 아니다. 다음 애노테이션을 붙인 클래스가 컴포넌트 스캔 대상에 포함된다.

  • @Component (org.springframework.stereotype 패키지)
  • @Controller (org.springframework.stereotype 패키지)
  • @Service (org.springframework.stereotype 패키지)
  • @Repository (org.springframework.stereotype 패키지)
  • @Aspect (org.aspectj.lang.annotation 패키지)
  • @Configuration (org.springframework.context.annotation 패키지)

@Aspect 애노테이션을 제외한 나머지 애노테이션은 실제로는 @Component 애노테이션에 대한 특수 애노테이션이다. 예를 들어 @Controller 애노테이션은 다음과 같다.

1
2
3
4
5
6
7
8
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller{
    @AliasFor(annotation = Component.class)
    String value() default "";
}

@Component 애노테이션이 붙어 있는데, 스프링은 @Controller 애노테이션이나 @Repository 애노테이션 등은 컴포넌트 스캔 대상이 될뿐만 아니라 스프링 프레임워크에서 특별한 기능과 연관되어 있다. 예를 들어 @Controller 애노테이션은 웹 MVC와 관련되어 있고 @Repository 애노테이션은 DB 연동과 관련 있다. 각 애노테이션의 용도는 관련 페이지에서 살펴보자.

4. 컴포넌트 스캔에 따른 충돌 처리

컴포넌트 스캔 기능을 사용해서 자동으로 빈을 등록할 때에는 충동에 주의해애 한다. 크게 빈 이름 충동수동 등록에 따른 충동이 발생할 수 있따.

4.1 빈 이름 충돌

spring 패키지와 spring2 패키지에 MemberRegsiterService 클래스가 존재하고 두 클래스 모두 @Component 애노테이션을 붙였다고 하자. 이 상태에서 다음 @ComponentScan 애노테이션을 사용하면 어덯게 될까?

1
2
3
4
5
@Configuration
@ComponentScan(basePackages = {"spring", "spring2"})
public class AppCtx{
    ...
}

위 설정을 이용해서 스프링 컨테이너를 생성하면 다음과 같이 익셉션이 발생한다.

이런 문제는 컴포넌트 스캔 과정에서 쉽게 발생할 수 있다. 이렇게 컴포넌트 스캔 과정에서 서로 다른 타입인데 같은 빈 이름을 사용하는 경우가 있다면 둘 중 하나에 명시적으로 빈 이름을 지정해서 이름 충돌을 피해야 한다.

4.2 수동 등록한 빈과 충돌

자동 등록된 빈의 이름은 클래스 이름의 첫 글자를 소문자로 바꾼다. 그런데 다음과 같이 설정 클래스에 직접 MemberDao 클래스를 “memberDao”라는 이름의 빈으로 등록하면 어떻게 될까?

1
2
3
4
5
6
7
8
9
@Configuration
@ComponentScan(basePackages = {"spring"})
public class AppCtx{
    @Bean
    public MemberDao memberDao(){
        MemberDao memberDao = new MemberDao();
        return memberDao;
    }
}

스캔할 때 사용하는 빈 이름과 수동 등록한 빈 이름이 같은 경우 수동 등록한 빈이 우선한다. 즉 MemberDao 타입 빈은 AppCtx에서 정의한 한 개만 존재한다.

Ref.

  • 최범균, 스프링프로그래밍입문5, 가메출판사.

카테고리:

업데이트: