1. 컴포넌트 스캔과 의존관계 자동 주입 시작하기
1-1. 기존 의존관계 주입 방법
- 지금까지 스프링 빈을 등록할 때는 자바 코드의
@Bean
이나 XML의 <bean>
등을 통해서 설정 정보에 직접 등록
[ AppConfig ]
public class AppConfig {
@Bean
public MemberSerivce memberService() {
return new MemberServiceImpl(memberRepository());
}
}
- 이것의 문제점
- 등록해야 하는 스프링 빈이 수백개라면 등록하기 힘들고 설정정보 커짐
- 스프링은 설정정보 없어도 자동으로 스프링빈을 등록하는 컴포넌트 스캔이라는 기능 제공
@Autowired
: 의존관계 자동 주입
1-2. 컴포넌트 스캔을 이용한 의존관계 주입
[ AutoAppConfig ]
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}
}
- 컴포넌트 스캔을 사용하려면 먼저
@ComponentScan
을 설정 정보에 붙여줘야함
- 기존의 AppConfig와는 다르게 @Bean으로 등록한 클래스가 하나도 없음
- 컴포넌트 스캔은
@Component
붙은 클래스를 스캔해서 스프링 빈으로 등록(@Configuration 안에 들어가보면 @Component 붙어있음)
@Autowired
를 이용해 여러 의존관계를 한번에 주입
[ MemoryMemberRepository ]
@Component
public class MemoryMemberRepository implements MemberRepository {}
[ RateDiscountPolicy ]
@Component
public class RateDiscountPolicy implements DiscountPolicy {}
[ OrderServiceImpl ]
@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;
}
}
- Test 시
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
사용
[ 자동 의존관계 주입 순서 ]
AutoAppConfig
클래스에 @ComponentScan
작성
- @ComponentScan 은 @Component 가 붙은 모든 클래스를 스프링 빈으로 등록
- 빈 이름 : 첫글자 소문자(memberServiceImpl) MemberServiceImpl 클래 - 빈 이름 직접 지정: @Component("memberService2")이런식으로 직접 지정하면 됨
- @Autowired 의존관계 자동 주입
- getBean(MemberRepository.class)와 동일
2. 탐색 위치와 기본 스캔 대상
2-1. 탐색할 패키지의 시작 위치 지정
@ComponentScan(
basePackages = "hello.core",
basePackageClasses = AutoAppConfig.class,
)
basePackages
: 시작점 찾기
- default는 이 파일이 있는 위치. 현재 hello.core바로 밑에 있으므로 default = hello.core임. 만약, member파일에서 시작하고 싶다면, hello.core.member라고 작성하면 됨.
- 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것
- 여러개 작성 가능
basePackageClasses
: 지정한 클래스의 패키지를 탐색 시작 위치로 지정
- 만약 지정하지 않으면
@ComponentScan
이 붙은 설정 정보 클래스의 패키지가 시작 위치가 됨
- 스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인
@SpringBootApplication
를 이
프로젝트 시작 루트 위치에 두는 것이 관례임(@SpringBootApplication 안에 @ComponentScan 들어있음
)
2-2. 컴포넌트 스캔 기본 대상
@Component
@Controlller
: 스프링MVC 컨트롤러로 인식
@Service
: 다른역할없이 비즈니스 계층 인식하는데 도움을 줌
@Repository
: 스프링 데이터 접근 계층으로 인식, 데이터계층의 예외를 스프링 예외로 변환
@Configuration
: 스프링 설정 정보로 인식, 스프링 빈이 싱글톤을 유지하도록 함
3. 필터
[ includeFilters ]
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
[ excludeFilters ]
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
[ BeanA ]
@MyIncludeComponent
public class BeanA {
}
[ BeanB ]
@MyExcludeComponent
public class BeanB {
}
[ excludeFilters ]
public class ComponentFilterAppConfigTest {
@Test
void filterScan() {
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
BeanA beanA = ac.getBean("beanA", BeanA.class);
Assertions.assertThat(beanA).isNotNull();
BeanB beanB = ac.getBean("beanB", BeanB.class);
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 {
}
}
4. 중복등록과 충돌
- 자동 빈 등록 vs 자동 빈 등록
- 컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 이름이 같은 경우 스프링은 오류 발생 시킴
- 수동 빈 등록 vs 자동 빈 등록
- 수동 빈이 자동 빈을 오버라이딩 해버려서, 수동 빈 등록이 우선권을 가짐