스프링에서 빈을 등록하는 방법으로 아래 두 가지 방법이 있었습니다.
@Configuration
클래스 내의 메소드에@Bean
을 명시해 주는 방법- XML
<bean>
을 이용해 명시하는 방법
그러나 스프링 에서는 위 두가지 정보가 없어도 스프링 빈을 등록하는 방법이 있습니다.
컴포넌트 스캔이란?
컴포넌트 스캔은 이름 그대로 @Component
어노테이션이 붙은 클래스를 찾아 스프링 빈으로 등록해 주는 것입니다. 이 컴포넌트 스캔 기능을 사용하기 위해서는 @ComponentScan
어노테이션을 설정 정보 클래스에 붙여주면 됩니다.
간단한 테스트를 해보겠습니다.
1. 두개의 컴포넌트 클래스를 작성
FruitRepository.class
@Component
public class FruitRepository {
public void save(Fruit fruit) {
System.out.println("fruit = " + fruit.getName());
}
}
FruitService.class
@Component
public class FruitService {
private final FruitRepository fruitRepository;
public FruitService(FruitRepository fruitRepository) {
this.fruitRepository = fruitRepository;
}
public void saveFruit(Fruit fruit) {
fruitRepository.save(fruit);
}
}
2. 컴포넌트 클래스를 스캔할 수 있는 설정 클래스 @Component 클래스를 작성
내부에는 아무런 내용도 작성하지 않았으며 클래스에 @ComponentScan 어노테이션을 작성하였습니다.
@Configuration
@ComponentScan
public class ComponentInfo {
}
3. 확인을 위한 테스트 코드를 작성
class ComponentInfoTest {
@Test
void componentScanTest() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ComponentInfo.class);
FruitService fruitService = ac.getBean(FruitService.class);
FruitRepository fruitRepository = ac.getBean(FruitRepository.class);
System.out.println("fruitRepository = " + fruitRepository);
System.out.println("fruitService = " + fruitService);
Assertions.assertThat(fruitService).isInstanceOf(FruitService.class);
Assertions.assertThat(fruitRepository).isInstanceOf(FruitRepository.class);
}
}
4. 테스트 결과 확인
- 테스트 성공 확인
- componentInfo, fruitRepository, fruitService 세개의 클래스가 싱글톤 빈으로 등록된 로그 확인
기존에 @Configuration
클래스에 메서드를 직접 작성하여 @Bean
을 등록했었습니다. 그러나 이번 테스트에서는 아무런 내용을 작성하지 않고 @Component
와 @ComponentScan
을 이용하여 자동으로 빈을 등록했습니다. 이때 스프링 빈의 이름은 클래스명의 맨 앞글자만 소문자로 변경되어 사용하는 것을 로그를 통해 확인할 수 있습니다.
- FruitRepository → fruitRepository
- FruitService → fruitService
기본으로 설정되는 빈 이름이 아닌 다른 이름을 사용하고 싶다면 @Component("빈이름")
으로 이름을 부여해줄 수 있습니다.
컴포넌트 스캔 범위
기본적으로 컴포넌트 스캔은 해당 설정 클래스 패키지부터 하위 패키지까지를 스캔합니다.
그러나 항상 전체 범위를 다 스캔하면 시간이 오래걸리며 특정 클래스 범위에 대해서만 설정하고 싶을 수 있습니다. 그럴 때는 컴포넌트 스캔의 basePackages
옵션을 사용하여 설정할 수 있습니다. FruitRepository를 repository 패키지 내로 이동시켜서 테스트 해보겠습니다.
1. 기존 ComponentInfo
클래스의 컴포넌트 스캔에 옵션을 설정
@ComponentScan(basePackages = “hello.core2.component.repository”)
@Configuration
@ComponentScan(basePackages = "hello.core2.component.repository")
public class ComponentInfo {
}
2. FruitService
는 repository 패키지 내에 존재하지 않으므로 빈으로 등록되지 않을 것입니다. 등록되지 않은 빈을 조회 시 NoSuchBeanDefinitionException
예외가 발생하게 됩니다. assertThrows를 이용하여 예외 테스트를 해보겠습니다.
@Test
void notFoundComponentTest() {
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentInfo.class);
assertThrows(NoSuchBeanDefinitionException.class, () -> ac.getBean(FruitService.class));
FruitRepository fruitRepository = ac.getBean(FruitRepository.class);
System.out.println("fruitRepository = " + fruitRepository);
assertThat(fruitRepository).isInstanceOf(FruitRepository.class);
}
3. 테스트 결과 확인
테스트가 성공하는 것을 확인하여 해당 예외가 발생했다는 것을 알 수 있습니다.
또한, 로그를 통해 FruitRepository
는 정상적으로 빈에 등록되어 호출되는 것을 확인할 수 있었습니다.
위에서 사용했듯이 대상 패키지를 정하는 방법은 basePackages를 사용하여 탐색 패키지를 직접 지정할 수 있고 basePackageClasses를 사용하여 해당 클래스가 포함된 클래스 패키지를 기준으로 정할 수도 있습니다.
설정 정보 위치
보편적으로 설정정보는 클래스의 최상단에 둡니다. 주로 사용하는 스프링 부트에서는 @SpringBootApplication
를 이 프로젝트 시작 루트 위치에 두는 것이 관례로, 이 어노테이션 내부에 @ComponentScan
이 들어있어 대표 시작 클래스에서 자동으로 컴포넌트들을 스캔해준다는 것을 알 수 있습니다.
컴포넌트 스캔 대상
스프링 부트 개발을 하다보면 @Component
보다는 @Controller
, @Service
, @Repository
등과 같은 계층을 나타내는 어노테이션을 주로 사용합니다. 이 클래스들의 내부를 보면 모두 @Component
이 붙어있는 것을 확인할 수 있습니다.
사실 자바 자체에서는 어노테이션은 상속 관계가 없습니다. 이처럼 어노테이션이 어떤 어노테이션의 속성을 가지고 있는지 알 수 있는 것은 자바 자체가 아닌 스프링에서 지원되는 기능임을 알아야합니다. 이외에도 어떤 어노테이션이 컴포넌트 대상이 되는지 확인해보겠습니다.
@Component
: 컴포넌트 스캔에서 사용@Controller
: 스프링MVC 컨트롤러에서 사용@Service
: 스프링 비즈니스 로직에 사용@Repository
: 스프링 데이터 접근 계층에서 사용@Configuration
: 스프링 설정 정보에서 사용
필터
SpringbootApplication 내의 @ComponentScan
을 보면 무언가 많은 옵션들이 존재하는 것을 볼 수 있습니다. 위 옵션들이 어떤 정보를 가지고 있는지 알아보겠습니다.
- includeFilters : 컴포넌트 스캔 대상을 추가로 지정
- excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정
FilterType 옵션
- ANNOTATION : 기본값, 애노테이션을 인식해서 동작한다.
- ASSIGNABLE_TYPE : 지정한 타입과 자식타입을 인식해서 동작한다.
- ASPOECTJ : AspectJ패턴 사용
- REGEX : 정규 표현식
- CUSTOM : TypeFilter라는 인터페이스를 구현해서 처리
'BackEnd > Spring' 카테고리의 다른 글
[Spring] 빈 스코프(BeanScope) (0) | 2024.06.17 |
---|---|
싱글톤 컨테이너(Singleton Container)란 무엇인가 (0) | 2024.06.03 |
[스프링] SpringContainer, BeanFactory, ApplicationContext (0) | 2024.05.21 |