@ComponentScan 의 속성을 통해 탐색 위치를 지정 할 수 있다.
Table of Content
@ComponentScan 의 범위 지정
basePackages 속성
통해 컴포넌트 스캔의 대상 패키지를 지정 할 수 있다. 명시된 패키지를 포함한 하위 패키지들을 탐색하면서 스프링 빈을 등록 하게 된다.
@Configuration
@ComponentScan(basePackages = "hello.core.member")
public class AutoAppConfig {
}
Java
복사
basePackages = "hello.core", “hello.service” 처럼 여러 시작 위치도 사용 할 수 있다.
basePackageClasses 속성
지정한 클래스의 패키지를 탐색 시작 위치로 지정하는 속성이다.
아래처럼 AutoAppConfig 클래스가 위치한 package hello.core 부터 컴포넌트 스캔의 대상이 된다.
package hello.core;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
@ComponentScan(
basePackageClasses = AutoAppConfig.class,
)
public class AutoAppConfig {
}
Java
복사
Default 속성
특별한 속성을 지정하지 않는 경우 어노테이션을 작성한 클래스의 패키지를 대상으로 하위 패키지들을 스캔하게 된다.(권장하는 방식)
package hello.core;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
@ComponentScan
public class AutoAppConfig {
}
Java
복사
이는 프로젝트 메인 어플리케이션 클래스에 붙이는 것으로 시작 루트 위치에 두는 것을 관례적으로 사용하고 있다. SpringBoot 프로젝트를 생성하면 메인 클래스에 @SpringBootApplication 어노테이션이 작성되어 있으며 @ComponentScan이 내부에 포함되어 있다.
@ComponentScan 의 대상
컴포넌트 스캔으로 스프링 빈으로 관리될 대상은 다음과 같은 어노테이션을 추가한다.
•
@Component : 컴포넌트 스캔의 기본
•
@Controller : 스프링 MVC 컨트롤러에 사용
•
@Service : 스프링 MVC 서비스, 비지니스 로직에 사용
•
@Repository : 스프링 MVC 리포지토리, 데이터 접근 계층에서사용
•
@Configuration : 스프링 설정 정보로 사용
위 MVC 계층과 Configuration 어노테이션의 내부를 살펴보면 @Component가 포함 된 것을 확인 할 수 있다.
위 어노테이션은 스프링이 부가기능을 수행한다.
•
@Controller : 스프링 MVC 컨트롤러로 인식
•
@Repository : 스프링 데이터 접근 계층으로 인식하며 데이터 계층의 예외를 스프링 예외로 변환해줌
•
@Configuration : 스프링 설정 정보로 사용되면서 스프링 빈이 싱글톤을 유지하도록 추가 처리
•
@Service : 특별한 처리는 하지 않지만, 개발자들이 명시적으로 핵심 비지니스 로직을 알 수 있도록 가독성을 위해 사용
@ComponentScan 의 필터
@ComponentScan에 @Filter 어노테이션을 통해서 특정 스캔 대상을 지정하거나 포함 할 수 있다.
public class ComponentFilterAppConfigTest {
@Test
void filterScan() {
//given
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
//when
BeanA beanA = ac.getBean("beanA", BeanA.class);
//then
assertThat(beanA).isNotNull();
assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("beanB", BeanB.class));
}
@Configuration
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
static class ComponentFilterAppConfig {
}
}
Java
복사
@MyIncludeComponent
public class BeanA {
}
Java
복사
@MyExcludeComponent
public class BeanB {
}
Java
복사
중복 등록과 충돌
자동빈등록 vs 자동빈등록 충돌
컴포넌트 스캔에 의해 자동으로 스프링빈이 등록되는데, 그 이름이 같은 경우 스프링은 오류를 발생시킨다. 예시로 강제로 동일한 빈 이름을 생성하기 위해서 MemberServiceImpl, OrderServiceImpl을 명시적으로 “service”라는 빈이 생성되도록 했다.
@Component("service")
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
...
Java
복사
@Component("service")
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
...
Java
복사
이렇게 의도적으로 충돌을 만드는 상황은 많지 않지만 이렇게 자동 빈 등록에서 명칭을 동일하게 생성하면 ConflictingBeanDefinitionException 으로 빈 정의의 문제가 발생하며 same name of class라는 중복에 대한 예외 메시지를 확인 할 수 있다.
수동 빈 등록 vs 자동 빈 등록 충돌
컴포넌트 스캔으로 자동으로 등록되는 memoryMemberRepository 빈과
@Component
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<>();
...
Java
복사
Config에서 수동으로 동일한 이름으로 memoryMemberRepository 빈의 이름을 동일하게 설정해보았다.
@Configuration
@ComponentScan(
basePackages = "hello.core.member",
basePackageClasses = AutoAppConfig.class,
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
@Bean(name = "memoryMemberRepository")
MemberRepository memberRepository() {
return new MemoryMemberRepository();
} // 수동 빈 정의와 자동 빈 정의 부분의 충돌 확인을 위한 코드 (이 경우 수동 등록 빈이 오버라이딩으로 우선권을 가짐)
}
Java
복사
이를 위한 테스트로 AutoAppConfig를 테스트로하는 스프링 컨테이너 빈 등록 테스트를 다시 실행해본다.
public class AutoAppConfigTest {
@Test
void basicScan() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
}
Java
복사
결과 다음처럼 자동 등록 빈을 수동 등록 빈이 Override하는 모습을 볼 수 있다. 수동 등록 빈이 우선권을 가지게 된다.
수동 빈 등록이 의도하지 않은 실수라면?
위 처럼 수동 빈 등록이 자동 빈 등록을 오버라이드 하는 상황은 의도한 결과지만 수많은 코드에서 의도하지 않은 오버라이드로 자동 빈 등록 객체를 잃어버린다면 찾아내기 어려운 상황이 발생할 수 있다. 이러면 코드상에서 오류를 발생시키지 않기 때문에 매우 찾기 어려운 버그로 발전할 가능성이 있다.
스프링 부트에서는 수동 빈 등록과 자동 빈 등록이 충돌나면 오류가 발생하도록 기본 값이 바뀌었다.
Spring으로 작동하는 테스트 코드 말고 SpringBoot 로 작동하는 어플리케이션 메인 메소드로 테스트하면 이 상태를 확인 할 수 있다.
@SpringBootApplication
public class CoreApplication {
public static void main(String[] args) {
SpringApplication.run(CoreApplication.class, args);
}
}
Java
복사
이처럼 동일한 이름의 빈이 중복되었다는 메시지가 나타난다.
혹시나 오버라이딩을 의도했다면 스프링부트 설정 파일인 application.properties에서
spring.main.allow-bean-definition-overriding=true
Java
복사
이 값을 true로 지정해주면 된다. 기본값은 false이다.
Related Posts
Search