@Bean
, XML의 <bean>
을 사용하여 등록하였다.@Autowired
기능을 제공한다.@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {}
AppConfig
클래스를 수정하지 않고 새로운 클래스를 선언하였다.@ComponentScan
을 설정정보에 추가한다.AppConfig
와 달리 @Bean
이 존재하지 않는다.@Component
어노테이션이 붙은 클래스를 스캔하여 스프링 빈으로 등록한다.@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration { ... }
@Configuration
의 내부를 보면, @Component
어노테이션이 존재하는 것을 확인할 수 있다.excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
@Configuration
어노테이션이 붙어있다.excludeFilters
를 사용하였다.@Component
public class MemoryMemberRepository implements MemberRepository {...}
@Component
public class RateDiscountPolicy implements DiscountPolicy {...}
@Component
를 추가해준다.@Component
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
@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;
}
...
}
AppConfig
에서는 @Bean
으로 직접 설정 정보를 작성했고, 의존관계도 직접 명시했다.@Autowired
를 사용해 자동으로 주입해준다.@Autowired
를 사용하여 생성자에서 여러 의존관계도 한번에 주입받는다.@Test
void basicScan(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService memberService = ac.getBean(MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
}
AnnotationConfigApplicationContext
를 사용하여 스프링 컨테이너를 생성한다.AutoAppConfig
클래스를 넘겨준다.16:22:04.759 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\SpringStudy\core\out\production\classes\hello\core\discount\RateDiscountPolicy.class]
16:22:04.765 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\SpringStudy\core\out\production\classes\hello\core\member\MemberServiceImpl.class]
16:22:04.767 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\SpringStudy\core\out\production\classes\hello\core\member\MemoryMemberRepository.class]
16:22:04.770 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\SpringStudy\core\out\production\classes\hello\core\order\OrderServiceImpl.class]
...
16:22:04.994 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'autoAppConfig'
16:22:05.004 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'rateDiscountPolicy'
16:22:05.006 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memberServiceImpl'
16:22:05.034 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memoryMemberRepository'
ClassPathBeanDefinitionScanner - Identified candidate component class
ClassPathBeanDefinitionScanner
가 컴포넌트 클래스를 식별함을 띄워주며, 해당 클래스의 위치를 알려준다.DefaultListableBeanFactory - Creating shared instance of singleton bean XXX
@ComponentScan
은 @Component
가 붙은 모든 클래스를 스프링 빈으로 등록시킨다.@Autowired
를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.getBean(MemberRepository.class)
와 동일하다고 이해하면 된다.@ComponentScan(
basePackages = "hello.core",
}
basePackages
: 탐색할 패키지의 시작 위치를 지정한다. 이 패키지를 포함해서 하위 패키지를 모두 탐색한다.basePackages = {"hello.core", "hello.service"}
basePackageClasses
: 지정한 클래스의 패키지를 탐색 시작 위치로 지정한다.@ComponentScan
이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다@Component
: 컴포넌트 스캔에서 사용@Controlller
: 스프링 MVC 컨트롤러에서 사용@Service
: 스프링 비즈니스 로직에서 사용@Repository
: 스프링 데이터 접근 계층에서 사용@Configuration
: 스프링 설정 정보에서 사용@Component
를 포함한다.@Controlller
: 스프링 MVC 컨트롤러로 인식@Service
: 개발자들이 핵심 비즈니스 로직이 여기에 있겠구나 라고 비즈니스 계층을 인식하는데 도움이 된다@Repository
: 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.@Configuration
: 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.includeFilters
: 컴포넌트 스캔 대상을 추가로 지정한다.excludeFilters
: 컴포넌트 스캔에서 제외할 대상을 지정한다@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {}
@MyIncludeComponent
public class BeanA {}
@MyIncludeComponent
를 적용한다@MyExcludeComponent
public class BeanB {}
@MyExcludeComponent
를 적용한다@Test
void filterScan(){
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
BeanA beanA = ac.getBean("beanA", BeanA.class);
assertThat(beanA).isNotNull();
org.junit.jupiter.api.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{}
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
ComponentFilterAppConfig
클래스를 설정 정보로 전달한다.@Configuration
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
static class ComponentFilterAppConfig{}
ComponentFilterAppConfig
클래스는 컴포넌트 스캔 방식으로 빈을 등록한다.@ComponentScan
의 속성으로 includeFilters
와 excludeFilters
를 지정하였다.includeFilters
에 MyIncludeComponent
애노테이션을 추가해서 BeanA가 스프링 빈에 등록된다.excludeFilters
에 MyExcludeComponent
애노테이션을 추가해서 BeanB가 스프링 빈에 등록되지 않는다.@Component
어노테이션이 포함된 클래스를 빈으로 등록하는데, includeFilters
속성을 통해 빈으로 등록시켰다.@Component
면 충분하기 때문에, includeFilters
를 사용할 일은 거의 없다. excludeFilters
는 여러가지 이유로 간혹 사용할 때가 있지만 많지는 않다컴포넌트 스캔에서 같은 이름을 가진 빈을 등록할 경우 문제가 발생한다.
@Component
) vs 자동 빈 등록 (@Component
)@Bean
) vs 자동 빈 등록 (@Component
)@Component
) vs 자동 빈 등록 (@Component
)@Component("service")
public class OrderServiceImpl implements OrderService{...}
@Component("service")
public class MemberServiceImpl implements MemberService{...}
OrderServiceImpl
을 스프링 빈으로 등록할 때 service
라는 이름으로 등록MemberServiceImpl
을 스프링 빈으로 등록할 때 service
라는 이름으로 등록... org.springframework.context.annotation.ConflictingBeanDefinitionException ...
ConflictingBeanDefinitionException
예외가 발생하게 된다.@Bean
) vs 자동 빈 등록 (@Component
)...
@ComponentScan
public class AutoAppConfig {
@Bean(name = "memoryMemberRepository")
MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
@Bean
을 사용하여 빈을 등록할 경우, @Component
에 의해 자동으로 등록된 빈과 중복이 발생한다.@Bean
)이 우선권을 갖는다Overriding bean definition for bean 'memoryMemberRepository' with a different definition: ...
The bean 'memoryMemberRepository', defined in class path resource [hello/core/AutoAppConfig.class] could not be registered
A bean with that name has already been defined in file and overriding is disabled.
출처: 인프런 스프링 핵심 원리 - 기본편 (김영한)
인프런 스프링 핵심 원리