본문 바로가기

Java/Spring

Spring 5 입문: Chapter 05.컴포넌트 스캔

더보기

이 프로젝트의 개발 환경

  • 개발 언어 및 개발 환경
    • OpenJDK 12
    • Spring: spring-context 5.0.2.RELEASE
    • Gradle 7.3
  • 기타 환경
    • macOS Sonoma 14.1.1
    • IntelliJ IDEA 2020.3 Ultimate Edition

컴포넌트 스캔은 자동 의존 주입과 함께 사용하는 기능입니다. 스프링이 직접 클래스를 검색해서 Bean으로 등록하도록 합니다.

설정 클래스에서 Bean으로 등록하지 않아도 되므로 컴포넌트 스캔을 사용하면 설정 코드가 확연히 줄어듭니다.

예제 프로젝트 생성

sp5-chap05 프로젝트를 생성하고 chap05 패키지를 추가합니다.

이전 프로젝트의 chap04 패키지의 소스 파일을 복사하여 사용합니다.

$ tree
.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
    └── main
        ├── java
        │   └── chap05
        │       ├── AppContext.java
        │       ├── ChangePasswordService.java
        │       ├── Main.java
        │       ├── Member.java
        │       ├── MemberDao.java
        │       ├── MemberInfoPrinter.java
        │       ├── MemberListPrinter.java
        │       ├── MemberPrinter.java
        │       ├── MemberRegisterService.java
        │       ├── MemberSummaryPrinter.java
        │       └── RegisterRequest.java
        └── resources

7 directories, 16 files

@Component으로 스캔 대상 지정

@Component 어노테이션이 등록된 클래스는 Spring의 스캔 대상이 됩니다.

기존 소스 파일에서 다음 클래스에 @Component를 등록합니다.

@Component
public class MemberDao { }

@ComponentScan
public class ChangePasswordService { }

@ComponentScan
public class MemberRegisterService { }

@Component("infoPrinter")
public class MemberInfoPrinter { }

@ComponentScan("listPrinter")
public class MemberListPrinter { }
코드 비고
Line 1:2 @Component 클래스를 스캔 대상으로 지정합니다.
Line 4:5
Line 7:8
Line 10:11 @Component("infoPrinter") 클래스를 스캔 대상으로 지정합니다.
어노테이션에서 지정하는 infoPrinterBean 이름입니다.
Line 13:14 @Component("listPrinter") 클래스를 스캔 대상으로 지정합니다.
어노테이션에서 지정하는 listPrinter Bean 이름입니다.

@ComponentScan으로 스캔 설정

@Component 어노테이션이 등록된 클래스를 Bean으로 등록하려면 설정 클래스에서 @ComponentScan을 적용해야 합니다.

설정 클래스 AppContext를 다음과 같이 수정합니다.

package chap05;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = {"chap05"})
public class AppContext
{
	@Bean
	public MemberPrinter memberPrinter()
	{
		return new MemberPrinter();
	}
	
	@Bean
	public MemberSummaryPrinter memberPrinterSeconds()
	{
		return new MemberSummaryPrinter();
	}
}
코드 비고
Line 8 @ComponentScan(basePackages = {"chap05"} 설정 클래스에서 컴포넌트 스캔을 활성화합니다.
어노테이션이 지정하는 basePackages컴포넌트를 찾기 위한 패키지 경로입니다.
Line 11:21 @Bean 컴포넌트 스캔 대상이 아닌 나머지 클래스를 Bean으로 등록합니다.
이제 컴포넌트 스캔 대상은 설정 클래스에서 관리하지 않습니다.

Main 클래스에서는 Bean 객체에 접근하는 코드는 다음과 같습니다.

ctx.getBean(MemberRegisterService.class);
ctx.getBean(ChangePasswordService.class);
ctx.getBean("listPrinter", MemberListPrinter.class);
ctx.getBean("infoPrinter", MemberInfoPrinter.class);
코드 비고
Line 1:2 getBean(MemberRegisterService.class) 컴포넌트 스캔으로 등록된 Bean 객체에 접근합니다.
@Component 어노테이션에서 Bean 이름을 지정하지 않았으므로 클래스로 접근합니다.
getBean(ChangePasswordService.class)
Line 3:4 getBean("listPrinter", MemberListPrinter.class) 컴포넌트 스캔으로 등록된 Bean 객체에 접근합니다.
@Component 어노테이션에서 지정하는 Bean 이름을 사용합니다.
getBean("infoPrinter", MemberInfoPrinter.class)

애플리케이션을 실행하고 명령문을 실행합니다. Bean 객체에 정상적으로 엑세스하고 있는지 확인합니다.

Bean이 제대로 등록되지 않았다면 No bean named '<Bean 이름>' available 오류가 발생합니다.

더보기

스캔 대상은 다음 어노테이션이 등록된 클래스입니다.

어노테이션 FQCN(Full Qualified Class Name)
@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
@Aspect org.aspectj.lang.annotation.Aspect

스캔 대상에서 제외하기 위한 excludeFilters 옵션

excludeFilters 속성을 사용해 스캔 대상을 제외 할 수 있습니다.

스캔에서 제외하기 위한 ExcludedMemberDao 클래스를 생성하고 @Component를 등록합니다.

그리고 AppContext@ComponentScan을 다음과 같이 수정합니다.

@Configuration
@ComponentScan(basePackages = {"chap05"}, excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "chap05\\..*ExcludedMemberDao"))
public class AppContext
{

}
코드 비고
Line 2 excludeFilters 자동으로 스캔되는 대상을 제외합니다.
type = FilterType.REGEX 정규표현식을 사용해서 제외 대상을 지정합니다.
pattern = "chap05\\..*ExcludedDao" chap05로 시작하고 ExcludedDao로 끝나는 대상을 정규표현식으로 지정합니다.

특정 어노테이션이 등록된 클래스를 스캔 대상에서 제외 할 수 있습니다.

ExcludedMemberDao 클래스에 @NoProduct 어노테이션을 등록하고 AppContext를 다음과 같이 수정합니다.

@Configuration
@ComponentScan(basePackages = {"chap05"}, excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = { NoProduct.class }))
public class AppContext
{

}
코드 비고
Line 2 type = FilterType.ANNOTATION 어노테이션이 등록된 대상을 제외합니다.
classes = { NoProduct.class } @NoProduct을 등록한 클래스를 대상에서 제외합니다.

특정 타입이나 그 하위 타입을 스캔 대상에서 제외 할 수 있습니다.

@Configuration
@ComponentScan(basePackages = {"chap05"}, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ExcludedMemberDao.class))
public class AppContext
{

}
코드 비고
Line 2 type = FilterType.ASSIGNABLE_TYPE 특정 타입인나 그 하위 타입을 대상에서 제외합니다.
classes = ExcludedMemberDao.class ExcludedMemberDao 타입 또는 ExcludedMemberDao를 상속받은 타입을 대상에서 제외합니다.
더보기

스캔 제외 대상 필터를 여러 개 등록하려면 excludeFilters 옵션을 배열로 사용합니다.

@Configuration
@ComponentScan(basePackages = {"chap05"}, excludeFilters = {
	@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = { NoProduct.class }),
	@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ExcludedMemberDao.class)
})
public class AppContext
{

}

정리 및 복습

  • 컴포넌트 스캔을 사용하면 스프링이 직접 클래스를 검색해서 Bean으로 등록하도록 합니다.
  • 스캔 대상이 되는 클래스는 @Component 어노테이션을 등록합니다.
  • @Component의 value 옵션에서 Bean 이름을 지정 할 수 있습니다.
  • 설정 클래스에서 @ComponentScan을 등록해 컴포넌트 스캔을 수행합니다.
  • @ComponentScan의 basePackages 옵션에서 스캔 대상을 검색하기 위한 패키지 경로를 지정합니다.
  • @ComponentScan의 excludedFilters 옵션에서 스캔 대상에서 제외하기 위한 필터를 지정합니다.
타입 비고 예시
FilterType.REGEX 정규표현식으로 필터합니다. type = FilterType.REGEX, pattern = "chap05\\..*ExcludedMemberDao"
FilterType.ANNOTATION 어노테이션이 등록된 대상을 필터합니다. type = FilterType.ANNOTATION, classes = { NoProduct.class }
FilterType.ASSIGNABLE_TYPE 특정 타입과 그 하위 타입을 필터합니다. type = FilterType.ASSIGNABLE_TYPE, classes = ExcludedMemberDao.class