Spring Framework

[Spring Core] Component Scan (@ComponentScan)

kgvovc 2021. 4. 5. 15:48
반응형

컴포넌트 스캔

컴포넌트 스캔(Component Scan)클래스 로더(Class Loader)를 스캔하면서 특정 클래스를 찾은 다음, DI 컨테이너에 등록하는 방법을 말한다.

 

 

 

[기본 설정으로 컴포넌트 스캔하기]

 

스캔 대상 애너테이션

별도의 설정이 없는 기본 설정에서는 다음과 같은 애너테이션이 붙은 클래스가 탐색 대상이 되고, 탐색된 컴포넌트는 DI 컨테이너에 등록된다.

  • @Component (org.springframework.stereotype.Component)
  • @Controller (org.springframework.stereotype.Controller)
  • @Service (org.springframework.stereotype.Service)
  • @Repository (org.springframework.stereotype.Repository)
  • @Configuration (org.springframework.context.annotation.Configuration)
  • @RestController (org.springframework.web.bind.annotation.RestController)
  • @ControllerAdvice (org.springframework.web.bind.annotation.ControllerAdvice)
  • @ManagedBean (javax.annotation.ManagedBean)
  • @Named (javax.inject.Named)

 

자바 기반 설정 방식에서는 컴포넌트 스캔을 하기 위해 @ComponentScan 애너테이션을 사용하고, XML 기반 설정 방식에서는 <context:component-scan> 요소를 사용한다.

 

컴포넌트 스캔을 할 때는 클래스 로더에서 위와 같은 애너테이션이 붙은 클래스를 찾아야 하기 때문에 탐색 범위가 넓고 처리하는 시간도 오래 걸린다. 이 시간은 결국 스프링 프레임워크를 사용한 애플리케이션의 기동 시간을 느리게 만드는 원인이 되기도 한다.

 

 

 

 

 

 

스캔 범위 설정

예를 들어, 다음과 같이 탐색 범위를 넓게 설정하는 것은 성능 면에서 좋지 않다.

 

  • 컴포넌트 스캔 범위가 넓은 경우
@ComponentScan(basePackages = "com")
@ComponentScan(basePackages = "com.example")

 

가능한 한 위와 같이 광범위한 범위 설정을 피해야 하는데, 통상 애플리케이션의 최상위나 한 단계 아래의 패키지까지를 스캔 대상으로 잡는 것이 적절하다.

 

  • 컴포넌트 스캔 범위가 적절한 경우
@ComponentScan(basePackages = "com.example.demo")
@ComponentScan(basePackages = "com.example.demo.app")

 

이때 basePackages 속성 대신 별칭으로 value 속성을 써도 된다. 어느 쪽을 사용해도 스캔하는 데는 문제가 없으나, 이 속성 자체를 생략할 경우 @ComponentScan이 설정된 클래스가 속한 패키지부터 그 하위 패키지를 스캔한다는 것에 유의하자.

 

 

 

 

 

 

 

대표적인 애너테이션

앞서 살펴본 스캔 대상 애너테이션 가운데 가장 많이 활용되는 네 가지는 다음과 같다.

 

대표적인 스캔 대상 애너테이션

애너테이션설명
@ControllerMVC 패턴에서 컨트롤러 역할을 하는 컴포넌트에 붙이는 애너테이션이다. 클라이언트에서 오는 요청을 받고, 비즈니스 로직의 처리 결과를 응답으로 돌려보내는 기능을 한다. 이때 실제 비즈니스 로직은 @Service가 붙은 컴포넌트에서 처리하도록 위임한다.
@Service비즈니스 로직(service)을 처리하는 컴포넌트에 붙이는 애너테이션이다. 컨트롤러에서 받은 입력 데이터를 활용해 비즈니스 로직을 실행하는 기능을 한다. 이때 영속적으로 보관해야 하는 데이터가 있다면 @Repository가 붙은 컴포넌트에서 처리하도록 위임한다.
@Repository영속적인 데이터 처리를 수행하는 컴포넌트에 붙이는 애너테이션이다. ORM(Object-Relational Mapping) 관련 라이브러리를 활용해 데이터의 CRUD를 처리하는 기능을 한다.
@Component위의 세 경우에 해당하지 않는 컴포넌트(유틸리티 클래스나 기타 지원 클래스 등)에 붙이는 애너테이션이다.

 

 

 

 

 

 

[필터를 적용한 컴포넌트 스캔]

앞서 살펴본 컴포넌트 스캔 대상 외에도 추가로 다른 컴포넌트를 더 포함하고 싶다면 필터를 적용하는 방법으로 스캔 범위를 커스터마이징할 수 있다. 스프링 프레임워크에서는 다음과 같은 필터를 제공한다.

  • 애너테이션을 활용한 필터(ANNOTATION)
  • 할당 가능한 타입을 활용한 필터(ASSIGNABLE_TYPE)
  • 정규 표현식 패턴을 활용한 필터(REGEX)
  • AspectJ 패턴을 활용한 필터

 

 

 

 

type = FilterType.ASSIGNABLE_TYPE

 

이제 필터를 사용하는 몇 가지 예를 살펴보자. 필터를 추가할 때는 includeFilters 속성에 나열하면 된다. 다음은 할당 가능한 타입을 필터로 활용한 것으로, 자바 기반 설정 방식으로 표현한 예다.

 

  • 할당 가능한 타입으로 필터링(인터페이스)
public interface DomainService {
	// 생략
}

 

  • 할당 가능한 타입으로 필터링(자바 기반 설정 방식)
@ComponentScan(basePackages = "com.example.demo" includeFilter = {
    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {DomainService.class})
})

 

이를 XML 기반 설정 방식으로 표현하면 다음과 같다.

 

  • 할당 가능한 타입으로 필터링(XML 기반 설정 방식)
<context:component-scan base-package="com.example.demo">
	<context:include-filter type="assignable" expression="com.example.demo.domain.DomainService" />
</context:component-scan>

 

 

 

 

 

 

 

type = FilterType.REGEX

 

다음은 정규 표현식 패턴을 필터로 활용한 것으로, 자바 기반 설정 방식으로 표현한 예다.

 

  • 정규 표현식 패턴으로 필터링(자바 기반 설정 방식)
@ComponentScan(basePackages = "com.example.demo",
             includeFilters = { @ComponentScan.Filter(type = FilterType.REGEX,
                    pattern = { ".+DomainService$" }) })

 

이를 XML 기반 설정 방식으로 표현하면 다음과 같다.

 

  • 정규 표현식 패턴으로 필터링(XML 기반 설정 방식)
<context:component-scan base-package="com.example.demo">
	<context:include-filter type="regex" expression=".+DomainService$" />
</context:component-scan>

 

 

여기서 한 가지 주의할 점은 필터를 적용해서 컴포넌트를 스캔할 때 앞서 살펴본 애너테이션이 붙은 스캔 대상도 함께 탐색 범위에 포함된다는 것이다. 만약 기본 설정에서 애너테이션이 붙은 스캔 대상을 무시하고, 순수하게 필터를 적용해서 탐색되는 컴포넌트만 사용하고 싶다면 다음과 같이 useDefaultFilters 속성을 false로 설정하면 된다.

 

  • 기본 스캔 대상을 제외하고 필터로만 스캔(자바 기반 설정 방식)
@ComponentScan(basePackages = "com.example.demo", useDefaultFilters = false,
             includeFilters = { @ComponentScan.Filter(type = FilterType.REGEX,
                    pattern = { ".+DomainService$"}) })

 

이를 XML 기반 설정 방식으로 표현하면 다음과 같다.

 

  • 기본 스캔 대상을 제외하고 필터로만 스캔(XML 기반 설정 방식)
<context:component-scan base-package="com.example.demo" use-default-filters="false">
	<context:include-filter type="regex" expression=".+DomainService$" />
</context:component-scan>

 

 

 

 

 

 

type = FilterType.ANNOTATION

기본 스캔 대상에 필터를 적용해 특정 컴포넌트를 추가하는 것과 반대로, 특정 컴포넌트를 스캔 대상에서 빼고 싶을 수도 있다. 이 경우에는 excludeFilters 속성을 활용한다. 예를 들어, 정규 표현식 패턴을 필터로 활용하면서 @Exclude 애너테이션(com.example.demo.Exclude)이 붙은 컴포넌트를 걸러내고 싶다면 다음과 같이 설정하면 된다.

 

  • 기본 스캔 대상과 특정 컴포넌트를 제외하고 필터로만 스캔(자바 기반 설정 방식)
@ComponentScan(basePackages = "com.example.demo", useDefaultFilters = false,
             includeFilters = { @ComponentScan.Filter(type = FilterType.REGEX,
                    pattern = { ".+DomainService$" }) },
             excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,
                    pattern = { Exclude.class}) })

 

  • 기본 스캔 대상과 특정 컴포넌트를 제외하고 필터로만 스캔(XML기반 설정 방식)
<context:component-scan base-package="com.example.demo" use-default-filters="false">
	<context:include-filter type="regex" expression=".+DomainService$" />
    <context:exclude-filter type="annotation" expression="com.example.demo.Exclude" />
</context:component-scan>

 

만약 포함(include)하는 필터와 제외(exclude)하는 필터 모두에 해당하는 컴포넌트가 있는 경우, 제외하는 필터가 포함하는 필터보다 우선순위가 높아 해당 컴포넌트는 스캔 대상에서 빠지고 결과적으로 DI 컨테이너에도 등록되지 않는다.

 

반응형