[스프링 핵심 원리 - 기본] 컴포넌트 스캔
인프런 김영한님 스프링 핵심 원리 강의를 듣고 정리한 글입니다.
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
컴포넌트 스캔과 의존관계 자동 주입 시작하기
지금까지는 스프링 빈을 등록할 때 @Bean이나 <bean>등을 사용하여 직접 나열하였다.
그래서 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.
의존관계를 자동으로 주입하는 Autowired를 제공한다.
컴포넌트 스캔을 사용하기 위해서는 @ComponentScan을 사용한다.
기존 예제 코드를 남겨두기 위해 excludeFilters를 사용했다.
(@Configuration안에 @Component가 들어있기 때문에 같이 조회가 된다.)
@Configuration
@ComponentScan(
// 직접 주입한 AppConfig도 불려오기 때문에 빼준 것
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}
그리고 MemoryMemberRepository와 RateDiscountPolicy, MemberServiceImpl에 @Component 어노테이션을 추가한다.
그 후 의존관계 주입을 AutoAppConfig에서 할 수 없기 때문에 이곳에 Autowired로 의존관계를 주입한다.
OrderServiceImpl 에도 Autowired를 추가한다.
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Autowired // ac.getBean(MemberRepository.class) 가 들어간다고 생각
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
// 테스트 용도
public MemberRepository getMemberRepository() {
return memberRepository;
}
}
기존과 동일하게 작용하는 것을 확인했다.
public class AutoAppConfigTest {
@Test
void basicScan() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService bean = ac.getBean(MemberService.class);
Assertions.assertThat(bean).isInstanceOf(MemberService.class);
}
}
1. @ComponentScan
이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다.
2. @Autowired 자동 주입
생성자에 @Autowired를 지정하면 스프링 컨테이너에 자동으로 해당 스프링 빈을 찾아서 주입한다.
이때 기본 조회 전략은 타입이 같은 빈을 찾아서 주입한다.
생성자에 파라미터가 많아도 알아서 잘 등록을 해준다.
탐색 위치와 기본 스캔 대상
이 패키지를 포함한 하위 패키지를 탐색하도록 설정 가능하다.
@ComponentScan(basePackages = "")
basePackageClass = '" 도 가능하다. (클래스 지정)
지정하지 않으면 얘가 있는 패키지 하위로 탐색한다.
package hello.springBasic;
@ComponentScan(
basePackages = "hello.springBasic.member",
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
김영한 님이 권장하는 방법
- 설정 정보 클래스의 위치를 최상단에 두고 basePackages 지정을 생략한다.
참고로 스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 @SpringBootApplication을 이 프로젝트 시작 루트 위치에 두는 것이 관례이고 이 설정 안에 바로 @ComponentScan이 들어있다.
컴포넌트 스캔 기본 대상
@Component 뿐아니라 @Controller(MVC 컨트롤러) @Service(비즈니스 로직) @Repository(데이터 접근 계층) @Configuration(스프링 설정 정보) 이들 또한 Component 스캔 대상이다.
필터
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
BeanB는 exclude annotation에 포함되어 있어서 조회가 불가능하다.
public class ComponentFilterAppConfigTest {
@Test
void filterScan() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
BeanA beanA = ac.getBean("beanA", BeanA.class);
Assertions.assertThat(beanA).isNotNull();
org.junit.jupiter.api.Assertions.assertThrows(
NoSuchBeanDefinitionException.class, () -> ac.getBean("beanB", BeanB.class)
);
}
@Configuration
@ComponentScan(
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
static class ComponentFilterAppConfig {
}
}
includeFilters와 excludeFilters를 사용할 일이 거의 없다.
중복 등록과 충돌
자동 빈 등록 vs 자동 빈 등록
- 컴포넌트 스캔에 의해 자동으로 스프링 빈에 등록이 되는데 그 이름이 같은 경우 스프링은 오류를 발생시킨다.
수동 빈 등록 vs 자동 빈 동록
- 수동 등록 빈이 우선권을 가진다.
Overriding bean definition for bean 'memoryMemberRepository' with a different definition: replacing [Generic bean: class [hello.springBasic.member.MemoryMemberRepository]
(수동 빈이 오버라이딩 해줌)
=> 이런 결과는 개발자 의도대로 일어나는 것이 아닌 실수로 일어나는 것이 많아 해결이 어려우니 최근 스프링 부트는 이런 상황에도 에러가 발생하도록 하였다.
혼자 개발하는 것이 아니므로 명확하게 사용하는 것이 좋다.