컴포넌트 스캔과 의존관계 자동 주입 시작하기
지금까지는 자바코드에 직접 @Bean을 통해 설정정보에 직접등록할 스프링빈을 나열했다.
이렇게하면 누락하는 문제도 발생.
그래서 스프링은 설정정보가 없어도 자동으로 스프링빈을 등록하는 컴포넌트스캔이라는 기능을 제공.
또의존관계 자동주입하는 @Autowired기능도 제공
기존 Appconfig 대신 AutoAppConfig생성
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)//예제를 안전하게 하기 위해 Configuration붙은 클래스는 제외
)// 자동으로 스프링빈에 등록
public class AutoAppConfig {
}
기존예제코드를 유지하기 위해 Filter로 Configuration 붙은 거는 거름.
@Component
public class MemberServiceImpl implements MemberService{
private MemberRepository memberRepository;
@Autowired
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);
}
}
그리고 기존 구현체들에 @Compnonent 붙여줌
그러나 Component 만 붙이면 빈으로 등록은 되지만 의존관계 설정이 되어있지 않음. 따라서 생성자에 위에 @Autowired를 통해 의존관계를 주입해줌.
@Autowired를 붙여주면 타입에 맞는 Bean을 주입해줌.
위 예제에서는 MemberRepository 타입인 구현체를 등록해줌.
(ac.getBean(MemberRepository.class 와같음)
@ComponentScan 은 @Componenet 가 붙은 모든클래스를 스프링 빈으로 등록
이 때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다.
default
ex) MemberServiceImpl 클래스 -> memberServiceImpl
이름 직접 지정
@Component("memberServiceImpl")
default 조회 전략
- 타입이 같은 빈을 찾아서 주입
- getBean(MemberRepository.class)와 동일하다고 이해하면 된다.
- 그렇다면 같은 타입이 여러개면?? -> 뒤에서 설명
탐색 위치와 기본 스캔 대상
basePackages를 탐색할 패키지시작점 설정
이 패키지를 포함하여 하위 패키지들을 모두 뒤진다.
basePackages = {hello.core, hello.service} 와 같이 시작포인트 여러개 설정 가능
basePackageClasses 를 통해 해당 클래스가 속해있는 패키지가 시작점 위치.
따로 지정하지 않으면 ComponentScan이 있는패키지가 시작 위치가 된다.
권장하는 방법
패키지 위치를 따로 지정하지 않고 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것.
참고로 스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 @SpringBootApplication 를 이 프로젝트 시작 루트 위치에 두는 것이 관례이다. (그리고 이 설정안에 바로 @ComponentScan 이 들어있다!)
컴포넌트 스캔 기본 대상
컴포넌트 스캔은 @Component 뿐만 아니라 다음과 같은 내용도 추가로 대상에 포함.
@Controller : Spring MVC컨트롤러에서 사용
@Service : 스프링 비즈니스 로직에 사용
@Repository : 스프링 데이터 접근 계층에 사용
@Configuration : 스프링 설정 정보에서 사용.
참고. 애노테이션에는 상속관계라는 것이 없음. 그래서 이렇게 애노테이션이 특정 애노테이션을 들고 있는 것을 인식할 수 있는 것은 자바 언어가 지원하는 기능이 아니라 스프링이 지원하는 기능이다.
필터
includeFilters : 컴포넌트 스캔 대상을 추가로지정
excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.
java/Hello/core/scan/filter/MyincludeComponent.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyincludeComponent {
}
include 어노테이션
java/Hello/core/scan/filter/MyexcludeComponent.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyexcludeComponent {
}
exclude annotation
@MyincludeComponent
public class BeanA {
}
BeanA 포함
@MyexcludeComponent
public class BeanB {
}
BeanB 제외
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{
}
}
Test 진행
FilterType옵션
ANNOTATION: 기본값, 애노테이션을 인식해서 동작한다.
ex) org.example.SomeAnnotation
ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작한다.
ex) org.example.SomeClass
ASPECTJ: AspectJ 패턴 사용
ex) org.example..*Service+
REGEX: 정규 표현식
ex) org\.example\.Default.*
CUSTOM: TypeFilter 이라는 인터페이스를 구현해서 처리
ex) org.example.MyTypeFilter
@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)
}
)
BeanA도 빼고 싶으면 다음과 같이 추가하면 된다.
중복 등록과 충돌
같은 빈이름을 등록하게 되면 어떻게 될까?
- 자동 빈등록 vs 자동 빈 등록
- 수동 빈 등록 vs 자동 빈 등록
자동 빈 등록 vs 자동 빈 등록
컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 그 이름이 같은 경우 스프링이 오류 발생
-> ConflictingBeanDefinitionException 예외 발생
수동 빈 등록 vs 자동 빈 등록
수동 빈 등록이 우선권을 가진다.
따라서 수동 빈이 자동빈을 오버라이딩 해버림.
개발자가 의도적으로 이런 결과를 기대하면, 자동 보다는 수동이 우선권을 가지는 것이 좋다.
하지만 여러 설정들이 꼬여서 이런 결과가 만들어지는 것이 대부분.
그러면 정말 잡기 어려운 버그가 만들어진다. 항상 잡기 어려운 버그는 애매한 버그이다.
그래서 최근 스프링부트에서는 수동 빈등록과 자동빈 등록이 충돌나면 오류가 발생하도록 기본값을 바꾸었다.
'Spring > 스프링 원리 - 기본편' 카테고리의 다른 글
[스프링-기본] 섹션5. 웹 애플리케이션과 싱글턴 (0) | 2023.01.23 |
---|---|
[스프링-기본] 섹션4. 스프링 컨테이너와 스프링 빈 (0) | 2023.01.22 |
[스프링-기본] 섹션2,3 스프링 핵심 원리 이해 (1) | 2023.01.19 |
[스프링-기본] 섹션1. 객체 지향 설계와 스프링 (0) | 2023.01.18 |