Blog

[SpringCore] 컴포넌트 스캔 - 스캔 범위 지정, 기본 스캔 대상

Category
Author
Tags
PinOnMain
1 more property
@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이다.
Search
 | Main Page | Category |  Tags | About Me | Contact | Portfolio