2022. 5. 15. 12:33ㆍLanguage`/Spring
컴포넌트 스캔
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy(){
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
}
이전까지는 따로 "스프링 설정 클래스"인 Config를 통해서 모든 빈을 수동으로 등록했고 @Configuration을 지정함에 따라서 스프링 컨테이너가 AppConfig 클래스를 읽어서 스프링 빈을 생성하고 의존관계도 주입해주었다
하지만 스프링 빈이 수십, 수백개가 된다면 일일이 다 등록을 하고 DI를 해주는 것은 개발자 입장에서 굉장히 귀찮은 일이 될 것이다
따라서 Spring은 따로 설정정보가 존재하지 않아도 스프링 빈으로 자동 등록해주는 "컴포넌트 스캔"이라는 기능을 제공한다
- 그에 따라서 의존관계도 자동으로 주입해주는 @Autowired 기능도 제공해준다
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Configuration.class
)
)
public class AutoAppConfig {
}
@ComponentScan을 스프링 설정 클래스에 붙여주면 알아서 "지정한 위치"에서부터 스캔을 시작한다
컴포넌트 스캔이 시작되면 @Component가 달린 모든 클래스에 대해서 스프링 빈으로 등록을 해준다
@Configuration도 @Component가 달려있고 위의 코드를 보게되면 "AppConfig"라는 스프링 설정 클래스에 현재 @Configuraion이 달려있는 것을 볼 수 있다
이러한 문제 때문에 AutoAppConfig에서는 @ComponentScan 파라미터에 "excludeFilters"라는 설정을 해줌에 따라서 @Configuration class는 필터링 안하겠다고 지정하였다
이렇게 스프링 빈으로 등록할 클래스에 대해서 @Component를 붙여주었다
이러고 나서 스프링 빈이 잘 등록이 되었나 테스트를 해보자
등록된 MemberService를 가져오려고 했으나 MemberService가 빈으로 등록이 되지 않아서 오류가 발생하였다
>> 여기서 우리가 파악해야 할 것은 스프링 설정 클래스의 위치이다
현재 내가 만든 AutoAppConfig라는 스프링 설정 클래스는 위치가 "Spring.Core.config"에 존재한다
@ComponentScan에서 "basePackages"에 아무것도 지정하지 않는다면 컴포넌트 스캔은 스프링 설정 클래스 파일이 존재하는 위치의 하위만 뒤지게 된다
따라서 "Spring.Core.config" 하위에는 우리가 @Component로 지정한 여러 클래스들이 존재하지 않는 것을 볼 수 있다
>> 따라서 "basePacakges"를 지정해줘야 한다
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Configuration.class
),
basePackages = "Spring.Core"
)
public class AutoAppConfig {
}
이렇게 지정하게 된다면 "Spring.Core" 하위에 존재하는 패키지{discount, member, order}를 뒤질 수 있기 때문에 위에서 @Component로 지정한 클래스들에 대한 스프링 빈이 등록이 된다
"basePackages"를 지정하니까 이제 스프링 빈 등록이 제대로 완료되었다
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Configuration.class
),
basePackages = {"Spring.Core.discount", "Spring.Core.member", "Spring.Core.order"}
)
public class AutoAppConfig {
}
이렇게 {}로 묶어서 여러개의 패키지로 지정할 수 있고 스프링 빈이 제대로 등록이 된다
최근 스프링이나 스프링부트에서 권장하는 방식은 아예 @ComponentScan이 붙은 스프링 설정 클래스를 "프로젝트 최상단"에 두는 방법이다
그리고 여기서 또 하나 확인해야할 점이 "등록된 스프링 빈의 이름"이다
이러한 클래스들에 대해서 @Component를 붙여줬고 그에 따라서 등록된 스프링 빈의 이름들은 다음과 같다
RateDiscountPolicy → rateDiscountPolicy
MemberServiceImpl → memberServiceImpl
MemoryMemberRepository → memoryMemberRepository
OrderServiceImpl → orderServiceImpl
전부 "맨 앞이 소문자로 변경"된 것을 볼 수 있다
이렇게 클래스에 @Component를 붙임에 따라서 스프링 빈으로 등록이 되면 그에 따른 이름은 맨 앞만 소문자로 바뀐 채 등록이 된다
물론 @Component(~~~~)방식으로 직접 이름을 지정할 수는 있지만 굳이 우리가 따로 이름을 지정해줄 필요는 없다
컴포넌트 스캔의 기본 대상은 다음과 같다
- @Component
- @Controller
- @Service
- @Repository
- @Configuraion
위의 Annotation들을 들어가서 보게되면 전부 @Component가 붙어있는 것을 확인할 수 있다
중복 등록 & 충돌
스프링 빈 이름이 중복 등록되어서 충돌이 발생하는 경우는 다음 2가지 경우이다
- 자동 빈 등록 vs 자동 빈 등록
- 수동 빈 등록 vs 자동 빈 등록
자동 vs 자동
@Component("Bean")
public class BeanFirst {
}
@Component("Bean")
public class BeanSecond {
}
public class ScanTest {
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Configuration.class
),
basePackages = "Spring.Core.scan"
)
static class TestConfig{
}
@Test
void test(){
ApplicationContext ac =
new AnnotationConfigApplicationContext(TestConfig.class);
BeanFirst b = ac.getBean(BeanFirst.class);
Assertions.assertThat(b).isInstanceOf(BeanFirst.class);
}
}
현재 BeanFirst, BeanSecond 클래스 둘 다 @Component에 의해서 자동 스캔 대상이고, 둘다 빈 이름을 "Bean"으로 등록되게 설정을 하였다
그리고나서 @ComponentScan을 통해서 빈으로 등록을 하려고 했고, 테스트해보니 다음과 같은 결과가 도출되었다
결국 "ConflictingBeanDefinitionException" 오류가 발생하게 되었고 "자동 vs 자동"의 경우 스프링 빈 이름이 중복되는 것들은 스프링 빈으로 등록이 되지 않는 것을 알 수 있다
수동 vs 자동
@Component("Bean")
public class BeanFirst implements BeanTest {
}
public class ScanTest {
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Configuration.class
),
basePackages = "Spring.Core.scan"
)
static class TestConfig{
@Bean(name = "Bean")
public BeanTest test(){
return new BeanFirst();
}
}
@org.junit.jupiter.api.Test
void test(){
ApplicationContext ac =
new AnnotationConfigApplicationContext(TestConfig.class);
BeanTest b = (BeanTest) ac.getBean("Bean");
Assertions.assertThat(b).isInstanceOf(BeanFirst.class);
}
}
"beanFirst"는 @Component에 의해 자동 스캔 대상이 되고, "beanTest"는 수동으로 빈 등록을 한 상태이다
이렇게 수동 빈 등록과 자동 빈 등록간에 스프링 빈 이름이 동일할 경우 다음과 같은 결과가 나온다
빈 이름이 동일한데도 불구하고 스프링 빈 등록이 정상적으로 이루어졌다
이 때 "Overriding bean definition"이 중요하다
결국 수동 빈 등록 & 자동 빈 등록간에 등록될 스프링 빈의 이름이 동일할 경우 "수동 빈 등록이 자동 빈 등록을 Overriding하게 된다"
따라서 수동 빈 등록이 우선권을 가진다고 볼 수 있다
하지만 이렇게 중복이 여러번 계속 발생하게 되면 여러 설정들이 꼬여서 원하는 결과를 도출해내지 못하거나 예외가 발생할 수 있다
따라서 최근 스프링 부트에서는 수동 빈 & 자동 빈 간의 충돌이 발생하면 default로 오류가 발생하도록 설정해두었다