스프링 핵심 원리 - 기본편의 복습을 위한 글이며
이 글에 나오는 모든 소스코드의 저작원은 인프런의 김영한 강사님께 있습니다.
1. 컴포넌트 스캔과 의존관계 자동 주입 시작하기
스프링 빈을 등록할 때 자바 코드의 @Bean
이나 XML의 <bean>
등을 통해 설정 정보에 직접 등록할 스프링 빈을 나열하였다. 그러나 이렇게 등록해야 할 스프링 빈이 수십, 수백개가 되면 일일이 등록하기가 어렵고 설정 정보가 커지거나 누락하는 문제가 발생할 수 있다.
스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 @ComponentScan
이라는 기능을 제공하고, 의존관계를 자동 주입해주는@Autowired
라는 기능도 제공한다.
코드로 살펴보자.
AutoAppConfig
@Configuration
@ComponentScan( excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
public class AutoAppConfig {
}
컴포넌트 스캔을 사용하게 되면 @Configuration
이 붙은 설정 정보도 자동으로 등록되기 때문에 앞서 만들어두었던 설정 정보도 함께 등록된다. 따라서 excludeFilters를 사용하여 설정 정보는 컴포넌트 스캔 대상에서 제외했다.
컴포넌트 스캔은 이름 그대로 @Component
어노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록한다. @Component
를 붙여주자.
❗️Tip
@Configuration이 컴포넌트 스캔의 대상이 된 이유는 @Configuration 소스코드를 열어보면 @Component 어노테이션이 붙어있기 때문이다.
MemoryMemberRepository - @Component 추가
@Component
public class MemoryMemberRepository implements MemberRepository{ ... }
RateDiscountPolicy - @Component 추가
@Component
public class RateDiscountPolicy implements DiscountPolicy { ... }
MemberServiceImpl - @Compenent, @Autowired 추가
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
OrderServiceImpl - @Component, @Autowired 추가
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
@Autowired
를 사용하면 생성자에서 여러 의존관계도 한 번에 주입받을 수 있다.
AutoAppConfigTest
package hello.core.scan;
public class AutoAppConfigTest {
@Test
void basicScan() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService memberService = ac.getBean(MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
}
}
로그를 살펴보면 컴포넌트 스캔이 정상적으로 작동된 것을 살펴볼 수 있다.
2. 컴포넌트 스캔 작동 방식
컴포넌트 스캔과 자동 의존관계 주입이 어떻게 동작하는지 그림으로 알아보자.
1) @ComponentScan
@ConponentScan
은 @Component
가 붙은 모든 클래스를 스프링 빈으로 등록한다.
- 빈 이름 기본 전략 : MemberServiceImpl 클래스 -> memberServiceImpl
- 빈 이름 직접 지정 : 만약 스프링 빈의 이름을 직접 지정하고 싶으면
@Component("memberService2")
이런식으로 이름을 부여하면 된다.
2) @Autowired 의존관계 자동 주입

생성자에@Autowired
를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다. 기본 조회 전략은 타입이 같은 빈을 찾아서 주입한다. getBean(MemberRepository.class)
와 동일하다고 이해하면 된다.
생성자에 파라미터가 많아도 다 찾아서 자동으로 주입한다.
2.1 탐색 위치와 기본 스캔 대상
모든 자바 클래스를 다 스캔하면 시간이 오래 걸리므로 스캔이 시작되는 지점을 지정해줄 수 있다.
@ComponentScan( basePackages = "hello.core", )
- basePackages : 탐색할 패키지의 시작 위치를 지정한다. 이 패키지를 포함한 하위 패키지들을 모두 탐색한다.
- basePackages = { "hello.core", "hello.service" } 처럼 여러 시작 위치를 지정할 수도 있다.
- basePackageClasses : 지정한 클래스의 패키지를 탐색 위치로 지정한다.
그러나, 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이 권장하는 방식이다. 예를 들어 프로젝트가 다음과 같이 구성되어 있으면,
- com.hello
- com.hello.service
- com.hello.repository
프로젝트 시작 루트인 com.hello에 AppConfig 같은 메인 설정 정보를 두고, @ComponentScan
어노테이션을 붙이고 basePackages 지정은 생략한다.
2.2 컴포넌트 스캔 기본 대상
컴포넌트 스캔은 @Component
뿐만 아니라 다음과 내용도 추가로 대상에 포함한다.
@Component
: 컴포넌트 스캔에서 사용@Controller
: 스프링 MVC 컨트롤러에서 사용@Service
: 스프링 비즈니스 로직에서 사용@Repository
: 스프링 데이터 접근 계층에서 사용@Configuration
: 스프링 설정 정보에서 사용
위 어노테이션은 여기에 자세하게 정리해두었다.
3. 필터
- includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.
- excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.
빠르게 예제로 살펴보겠다.
컴포넌트 스캔 대상에 추가할 어노테이션
package hellp.re_core.scan.filter;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
컴포넌트 스캔 대상에서 제외할 어노테이션
package hellp.re_core.scan.filter;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
컴포넌트 스캔 대상에 추가할 클래스
package hellp.re_core.scan.filter;
@MyIncludeComponent
public class BeanA {
}
컴포넌트 스캔 대상에서 제외할 클래스
package hellp.re_core.scan.filter;
@MyExcludeComponent
public class BeanB {
}
설정 정보와 전체 테스트 코드
package hello.core.scan.filter;
public class ComponentFilterAppConfigTest {
@Test
void filterScan() {
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
BeanA beanA = ac.getBean("beanA", BeanA.class);
assertThat(beanA).isNotNull();
Assertions.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 {
}
}
- includeFilters에 MyIncludeComponent 어노테이션을 추가해서 BeanA가 스프링 빈에 등록된다.
- excludeFilters에 MyExcludeComponent 어노테이션을 추가해서 BeanB는 스프링 빈에 등록되지 않는다.
3.1 FilterType 옵션
- ANNOTATION : 기본값, 어노테이션을 인식해서 동작한다.
- ASSIGNABLE_TYPE : 지정한 타입과 자식 타입을 인식해서 작동한다.
- ASPECTJ : AspectJ 패턴 사용
- REGEX : 정규 표현식
- CUSTOM : TypeFilter라는 인터페이스를 구현해서 처리
예를 들어서 BeanA도 빼고 싶으면 다음과 같이 추가하면 된다.
@ComponentScan(
includeFilters = { @Filter(type = FilterType.ANNOTATION,
classes = MyIncludeComponent.class),
},
excludeFilters = { @Filter(type = FilterType.ANNOTATION,
classes = MyExcludeComponent.class),
@Filter(type = FilterType.ASSIGNABLE_TYPE,
classes = BeanA.class)
}
)
4. 중복 등록과 충돌
컴포넌트 스캔에서 같은 빈 이름을 등록하는 경우는 다음 두 가지가 있다.
- 자동 빈 등록 vs 자동 빈 등록
- 수동 빈 등록 vs 자동 빈 등록
4.1 자동 빈 등록 vs 자동 빈 등록
컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 그 이름이 같은 경우 스프링은 ConflictingBeanDefinitionException 오류를 발생시킨다.
4.2 수동 빈 등록 vs 자동 빈 등록
위의 경우는 다음과 같은 경우이다.
@Component
public class MemoryMemberRepository implements MemberRepository {}
@Configuration
@ComponentScan(excludeFilters =
@Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
public class AutoAppConfig {
@Bean(name = "memoryMemberRepository")
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
이러한 경우에는 수동 빈 등록이 우선권을 가진다. 즉, 수동 빈이 자동 빈을 오버라이딩 해버린다.
개발자가 의도했다면 당연히 수동이 우선권을 가지는 것이 좋다. 그러나 대부분은 여러 설정들이 꼬여서 발생하는 경우가 대부분이다. 정말 잡기 어려운 버그가 발생하니깐 조심하는것이 좋다.
잘못된 정보가 있거나 오타가 있으면 댓글 달아주세요!
감사합니다 :)
'Backend > Spring - Core' 카테고리의 다른 글
Spring #9. 빈 생명주기 콜백 (0) | 2023.01.15 |
---|---|
Spring #8. 다양한 의존관계 주입 방법 (0) | 2023.01.15 |
Spring #6. 싱글톤 컨테이너 (0) | 2023.01.12 |
Spring #5. BeanFactory와 ApplicationContext (0) | 2022.12.29 |
Spring #4. 스프링 컨테이너와 스프링 빈 (0) | 2022.12.25 |