s
s
spring
Search…
Component Scan
스프링 빈을 등록할 때는 자바 코드의 @Bean이나 XML의 <bean> 등을 통해서 설정 정보에 직접 등록할 스프링 빈을 나열하여 등록할 수 있다. 하지만 이렇게 등록해야 할 스프링 빈이 수십, 수백개가 되면 일일이 등록하기도 힘들며, 설정 정보도 커지고, 누락하는 문제도 발생한다.
1
@Configuration
2
public class AppConfig {
3
4
@Bean
5
public MemberService memberService() {
6
return new MemberServiceImpl(memberRepository());
7
}
8
9
@Bean
10
public MemberRepository memberRepository() {
11
return new MemoryMemberRepository();
12
}
13
}
Copied!
스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.
@ComponentScan : @Component 어노테이션이 붙은 빈을 다 등록해준다.
  • excludeFilters : 제외할 Component 설정
  • includeFilters : 포함할 Component 설정
  • basePackages : 탐색할 기본 패키지 경로(설정 안한 경우 해당 어노테이션 패키지 하위로 설정)
    • basePackages = {"dh0023.springcore.order", "dh0023.springcore.member"} : 여러 시작 위치 지정가능
  • basePackageClassses : 지정한 클래스의 패키지를 탐색 시작 위치로 지정(default : @ComponentScan 이 붙은 설정 정보 클래스 패키지 하위로 설정)
1
package dh0023.springcore.config;
2
3
import org.springframework.context.annotation.ComponentScan;
4
import org.springframework.context.annotation.Configuration;
5
import org.springframework.context.annotation.FilterType;
6
7
/**
8
* @ComponentScan은 @Component 어노테이션이 붙은 클래스를 빈으로 등록해준다.
9
* 기본패키지를 설정해주지 않으면, 현재 패키지 하위로 설정된다.
10
* 예외하고 싶은 클래스가 있는 경우 excludeFilters로 설정할 수 있다.
11
*/
12
@Configuration
13
@ComponentScan(
14
basePackages = "dh0023.springcore",
15
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
16
)
17
public class AutoAppConfig {
18
19
}
Copied!
그러면 의존관계는 어떻게 주입하는 걸까? @Autowired 로 의존관계를 자동으로 주입할 수 있다.
@Bean 으로 생성해 직접 의존관계를 설정하던 코드에서, @Component@Autowired 만으로 의존관계와 빈으로 등록할 수 있다.
1
package dh0023.springcore.member.service;
2
3
import dh0023.springcore.member.domain.Member;
4
import dh0023.springcore.member.repository.MemberRepository;
5
import org.springframework.beans.factory.annotation.Autowired;
6
import org.springframework.stereotype.Component;
7
8
@Component
9
public class MemberServiceImpl implements MemberService{
10
11
/**
12
* 생성자 DI를 통해 구현클래스 의존성 제거 => 실행에만 집중 가능
13
*/
14
private final MemberRepository memberRepository;
15
16
/**
17
* Autowired로 자동 의존관계 주입 가능
18
*/
19
@Autowired
20
public MemberServiceImpl(MemberRepository memberRepository) {
21
this.memberRepository = memberRepository;
22
}
23
}
Copied!
  • 별도로 빈 이름을 설정하고 싶은 경우에는 @Component("설정할 빈 이름") 과 같이 설정할 수 있다.
  • @Autowired 를 지정하면 스프링 컨테이너가 해당 스프링 빈을 찾아서 주입하는데 이때, 타입이 같은 빈을 찾아서 주입을 한다.

FilterType 옵션

type
설명
ANNOTATION
default 어노테이션을 인식해서 동작
type = FilterType.ANNOTATION, classes = Configuration.class
ASSIGNABLE_TYPE
지정한 타입과 자식 타입을 인식해서 동작 클래스 직접 지정
org.example.ExampleService
ASPECTJ
AspectJ 패턴 사용
org.example..*Service+
REGEX
정규 표현식
org.example.Default.*
CUSTOM
TypeFilter 이라는 인터페이스를 구현해서 처리
org.example.MyTypeFilter

@ComponentScan 권장 위치

@ComponentScan 권장 위치

패키지 위치를 별도로 지정하지 않고, 설정 정보 클래스 위치를 프로젝트 최상단에 두는 것을 권장한다.(스프링 부트도 이 방법으로 시작)
프로젝트 메인 설정 정보는 프로젝트를 대표하는 정보이기 때문에 프로젝트 시작 루트 위치에 두는 것을 권장한다.
1
@Target(ElementType.TYPE)
2
@Retention(RetentionPolicy.RUNTIME)
3
@Documented
4
@Inherited
5
@SpringBootConfiguration
6
@EnableAutoConfiguration
7
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
8
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
9
public @interface SpringBootApplication {
10
11
/**
12
* Exclude specific auto-configuration classes such that they will never be applied.
13
* @return the classes to exclude
14
*/
15
@AliasFor(annotation = EnableAutoConfiguration.class)
16
Class<?>[] exclude() default {};
17
18
/**
19
* Exclude specific auto-configuration class names such that they will never be
20
* applied.
21
* @return the class names to exclude
22
* @since 1.3.0
23
*/
24
@AliasFor(annotation = EnableAutoConfiguration.class)
25
String[] excludeName() default {};
26
...
27
}
Copied!
스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 @SpringBootApplication 안에 @ComponentScan 이 포함되어있으며, 보통 최상단에 해당 클래스가 위치해있다.

@ComponentScan 대상

@Component 뿐만 아니라 다른 어노테이션들도 추가로 대상에 포함된다.
예를 들어 @Configuration 어노테이션을 살펴보자.
1
@Target(ElementType.TYPE)
2
@Retention(RetentionPolicy.RUNTIME)
3
@Documented
4
@Component
5
public @interface Configuration {
6
//...
7
}
Copied!
해당 어노테이션 내부에 @Component 어노테이션을 포함하고 있는 것을 볼 수 있다. @ComponentScan@Component 어노테이션이 붙어있는 클래스는 모두 빈으로 등록하므로, @Controller, @Service, @Repository 등등 어노테이션이 붙은 클래스도 빈으로 등록하는 것을 알 수 있다.
  • 어노테이션에는 상속관계라는 것이 없으며, 특정 애노테이션을 들고 있는 것을 인식할 수 있는 것은 자바 언어가 지원하는 기능은 아니고, 스프링이 지원하는 기능이다.

중복 등록과 충돌

자동빈등록 vs 자동 빈등록

컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 그 이름이 같은 경우 스프링은 ConflictingBeanDefinitionException 예외 발생시킨다.
이러한 경우는 거의 발생하지 않는다.

수동 빈 등록 vs 자동 빈 등록

1
@Component
2
public class MemoryMemberRepository implements MemberRepository {}
Copied!
1
@Configuration
2
@ComponentScan(
3
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
4
)
5
public class AutoAppConfig {
6
7
@Bean(name = "memoryMemberRepository")
8
public MemberRepository memberRepository() {
9
return new MemoryMemberRepository();
10
}
11
}
Copied!
같은 이름으로 수동빈과 자동빈이 등록된 경우에는, 수동 빈등록이 우선권을 가진다.
1
Overriding bean definition for bean 'memoryMemberRepository' with a different
2
definition: replacing
Copied!
수동빈이 자동 빈을 오버라이딩 한다.
최근 스프링 부트에서는 수동 빈 등록과 자동 빈 드옭이 충돌나면 오류가 발생하도록 기본 값을 바꾸었으며, 만약 오버라이딩을 가능하게 하고 싶으면 spring.main.allow-bean-definition-overriding=true 로 옵션을 설정하라고 가이드를 주고 있다.
1
Consider renaming one of the beans or enabling overriding by setting
2
spring.main.allow-bean-definition-overriding=true
Copied!

테스트 코드로 확인

기본 @ComponentScan 빈등록 확인

1
package dh0023.springcore.scan;
2
3
import dh0023.springcore.config.AutoAppConfig;
4
import dh0023.springcore.member.service.MemberService;
5
import org.assertj.core.api.Assertions;
6
import org.junit.jupiter.api.Test;
7
import org.springframework.context.ApplicationContext;
8
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
9
10
public class AutoAppConfigTest {
11
12
@Test
13
void basicScan() {
14
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
15
16
MemberService memberService = ac.getBean(MemberService.class);
17
18
Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
19
}
20
}
Copied!
1
23:44:34.697 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
2
23:44:34.701 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
3
23:44:34.713 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
4
23:44:34.716 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
5
23:44:34.739 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'autoAppConfig'
6
23:44:34.748 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'rateDiscountPolicy'
7
23:44:34.749 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memoryMemberRepository'
8
23:44:34.750 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memberServiceImpl'
9
23:44:34.858 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'memberServiceImpl' via constructor to bean named 'memoryMemberRepository'
10
23:44:34.860 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'orderServiceImpl'
11
23:44:34.865 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'memoryMemberRepository'
12
23:44:34.866 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'rateDiscountPolicy'
Copied!
로그를 보면 singleton bean이 등록되는 것을 볼 수 있으며, Autowired도 확인할 수 있다.

예외/포함 확인

  • MyExcludeComponent
1
package dh0023.springcore.scan.filter;
2
3
import java.lang.annotation.*;
4
5
@Target(ElementType.TYPE)
6
@Retention(RetentionPolicy.RUNTIME)
7
@Documented
8
public @interface MyExcludeComponent {
9
}
Copied!
  • MyIncludeComponent
1
package dh0023.springcore.scan.filter;
2
3
import java.lang.annotation.*;
4
5
@Target(ElementType.TYPE)
6
@Retention(RetentionPolicy.RUNTIME)
7
@Documented
8
public @interface MyIncludeComponent {
9
}
Copied!
  • BeanA
1
package dh0023.springcore.scan.filter;
2
3
@MyIncludeComponent
4
public class BeanA {
5
}
Copied!
  • BeanB
1
package dh0023.springcore.scan.filter;
2
3
@MyExcludeComponent
4
public class BeanB {
5
}
Copied!
  • Test
1
package dh0023.springcore.scan.filter;
2
3
import org.assertj.core.api.Assertions;
4
import org.junit.jupiter.api.Test;
5
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
6
import org.springframework.context.ApplicationContext;
7
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
8
import org.springframework.context.annotation.ComponentScan;
9
import org.springframework.context.annotation.Configuration;
10
import org.springframework.context.annotation.FilterType;
11
12
import static org.assertj.core.api.Assertions.*;
13
import static org.junit.jupiter.api.Assertions.*;
14
15
public class ComponentFilterAppConfigTest {
16
17
@Test
18
void filterScan() {
19
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
20
21
BeanA beanA = ac.getBean("beanA", BeanA.class);
22
assertThat(beanA).isNotNull();
23
24
assertThrows(
25
NoSuchBeanDefinitionException.class, () -> ac.getBean("beanB", BeanB.class)
26
);
27
28
29
}
30
31
@Configuration
32
@ComponentScan(
33
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
34
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
35
)
36
static class ComponentFilterAppConfig {
37
}
38
}
Copied!

참고