컴포넌트 스캔은 스프링이 직접 클래스를 검색해서 빈으로 등록해주는 기능이다. “@Component” 어노테이션을 붙여주면 되는데, 우리가 흔히 사용하는 @Controller, @Service, @Configuration내에 @Component 어노테이션이 포함되어 있기 때문에 스프링이 해당 클래스를 빈으로 등록할 수 있던 것이다.
💡 Without 컴포넌트 스캔자바 코드의 @Bean 이나 XML에 직접 등록할 스프링 빈을 나열해야 한다. (**ch04 AppCtx** 참고)
이를 개발자가 직접 관리하는 것은 어렵기 때문에, 설정 정보가 없어도 스프링에서 자동으로 빈을 등록할 수 있도록 컴포넌트 스캔 기능을 제공한다.
스프링이 자동으로 빈을 등록하기 위해서는 등록하려는 클래스에 @Component 어노테이션을 붙여야 한다.
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
빈 이름 전략
- 기본 : 수동으로 이름을 지정하지 않을 시, 가장 앞 문자를 소문자로 바꾼 것이 빈 이름이 된다.
- 수동 지정 :
@Component("지정할 이름")
- ex) @Component(”listPrinter”)
기본적인 컴포넌트 스캔 대상
@Component
: 컴포넌트 스캔에서 사용@Controller
: 스프링 MVC 컨트롤러에서 사용@Service
: 스프링 비즈니스 로직에서 사용@Repository
: 스프링 데이터 접근 계층에서 사용@Configuration
: 스프링 설정 정보에서 사용
컴포넌트 스캔 범위
@ComponentScan
어노테이션이 있는 파일의 패키지 아래를 찾는다.basePackages
/basePackageClasses
로 지정도 가능- 권장 방법 : 구성파일에 등록시 프로젝트 최상단에 두기 →
@SpringBootApplication
에 포함되어있어서 자동으로 최상단으로 유지된다
@ComponentScan
은@Component
가 붙은 모든 클래스를 스프링 빈으로 등록한다.- 빈 이름: (1) 클래스 명에서 맨 앞글자만 소문자로 변경 (2) 수동 지정
→ main 메소드에 @SpringbootApplication 어노테이션을 붙였기 때문이다.
해당 어노테이션에 @ComponentScan이 포함되어있음을 확인할 수 있다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication
탐색할 패키지의 시작 위치 지정: basePackages, basePackagesClasses
모든 자바 클래스를 다 컴포넌트 스캔하면 비효율적이다. 따라서 꼭 필요한 위치부터 탐색할 수 있도록 시작 위치를 지정할 수 있다.
@ComponentScan(
basePackages = "hello.core"
)
- 만약 basePackagesClasses를 따로 지정하지 않으면, @ComponentScan이 붙은 설정 정보 클래스의 패키지가 탐색 시작 위치가 된다.
- 위에서도 언급했지만, 패키지 위치를 지정하지 않고, 설정 정보 클래스를 프로젝트 최상단에 두는 것이다.
- 프로젝트 최상단에 설정정보 위치시키면 해당 패키지를 포함한 하위 패키지는 모두 자동으로 컴포넌트 스캔의 대상이 된다.
Filter 속성 : 컴포넌트 스캔 대상 추가/제외
해당 속성을 사용하면 스캔할 때 특정 대상을 자동 등록 대상에서 추가/ 제외할 수 있다.
-
includeFilters : 컴포넌트 스캔 대상으로 추가
-
excludeFilters : 컴포넌트 스캔 대상에서 제외
-
FilterType 옵션
ANNOTATION
: 기본값, 어노테이션을 인식해 동작ASSIGNABLE_TYPE
: 지정한 타입과 자식 타입을 인식해 동작ASPECTJ
: AspectJ 패턴 사용REGEX
: 정규 표현식CUSTOM
: TypeFilter이라는 인터페이스를 구현해서 처리
Example
@Configuration
@ComponentScan(basePackages = {"spring"},
excludeFilters = @Filter(type = FilterType.REFEX, pattern = "spring\\..*Dao"))
public class AppCtxWithExclude {
}
- REGEX type으로 제외 대상을 지정한다.
- “spring.”으로 시작하고 “Dao”로 끝나는 클래스들을 컴포넌트 스캔 대상에서 제외한다.
@Configuration
@ComponentScan(basePackages = {"spring"},
excludeFilters = @Filter(type = FilterType.ASPECTJ, pattern = "spring.*Dao" ) )
public class AppCtxWithExclude {
}
- spring 패키지의 Dao로 끝나는 타입을 컴포넌트 스캔 대상에서 제외한다.
@Configuration
@ComponentScan(basePackages = {"spring", "spring2" },
excludeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = {NoProduct.class, ManualBean.class} )
})
public class AppCtxWithExclude {
}
- @NoProduct, @ManualBean 어노테이션이 붙은 클래스를 컴포넌트 스캔 대상에서 제외한다.
만약 2가지 이상의 filter type을 사용하고 싶다면, 여러 개의 @Filter 속성을 작성하면 된다!
컴포넌트 스캔 기능을 사용해서 자동으로 빈을 등록할 때는 충돌을 주의해야 한다.
크게 (1) 빈 이름 충돌과 (2) 수동 등록에 따른 충돌이 발생할 수 있다.
💡 무슨 일이 있어도 특정 이름의 빈은 오직 1개만 등록되어야 한다.컴포넌트 스캔에서 같은 빈이 등록될 경우가 두 가지 존재한다
- 자동 빈 등록 & 자동 빈 등록
- 수동 빈 등록 & 자동 빈 등록
자동 빈 등록 & 자동 빈 등록
이 경우 스프링에서 다음 Exception을 발생시킨다.
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [hello.core.AutoAppConfig]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'service' for bean class [hello.core.order.OrderServiceImpl] conflicts with existing, non-compatible bean definition of same name and class [hello.core.member.MemberServiceImpl]
수동 빈 등록 & 자동 빈 등록
- 수동 빈 등록이 우선권을 가진다.
1. @ComponentScan으로 자동 등록되는 Bean
@Component
public class MemoryMemberRepository implements MemberRepository{
}
2. 수동 등록 Bean - 설정 파일에@Bean으로 등록
public class AutoAppConfig {
@Bean(name = "memoryMemberRepository")
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
→ memoryMemberRepository 타입의 Bean은 오직 AutoAppConfig에서 정의한 Bean 한 개만 존재한다!