출처 : 김영한님의 스프링 핵심 원리 - 기본편 인프런 강의
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
컴포넌트 스캔과 의존관계 자동 주입
지금까지는 @Bean으로 직접 설정 정보 AppConfig로 스프링 빈을 나열했다. 하지만 등록해야할 스프링 빈이 많아지면 설정 정보도 커지고 누락되기도 쉬운 문제가 발생한다.
그래서 스프링은 이런 문제를 해결하기 위해 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다. 또한 의존관계도 자동으로 주입해주는 @Autowired도 제공한다.
@ComponentScan은 @Component가 붙은 클래스를 스캔해서 스프링 빈으로 등록한다. 각 클래스 MemoryMemberRepository, RateDiscountPolicy, MemberServiceImpl, OrderServiceImpl에 컴포넌트 스캔의 대상이 되도록 @Component를 붙여준다.
그리고 MemberServiceImpl, OrderServiceImpl은 의존관계 주입이 필요하므로 @Autowired도 함꼐 추가해준다. 의존관계를 기존의 AppConfig처럼 나열하는 것이 아닌 클래스 내부에서 의존관계 주입을 해결하도록 하는 것이다.
@Component는 자동으로 빈을 등록해주기 때문에 의존관계를 따로 명시할 방법이 없어서 제공되는 @Autowired 어노테이션을 이용해 의존관계를 주입한다.
@Autowired가 마치 ac.getBean(MemberRepository.class)의 동작을 하는 것
자동으로 스프링 빈이 잘 등록되는 지 테스트 해보자
public class AutoAppConfigTest {
@Test
void basicScan() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
}
로그를 잘 살펴보면 컴포넌트 스캔이 잘 동작하는 것을 확인할 수 있고, 자동 의존관계 주입도 어떻게 이루어져 있는 지 확인할 수 있다.
21:30:26.522 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'fixDiscountPolicy'
21:30:26.524 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'rateDiscountPolicy'
21:30:26.524 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memberServiceImpl'
21:30:26.540 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memoryMemberRepository'
21:30:26.541 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'memberServiceImpl' via constructor to bean named 'memoryMemberRepository'
21:30:26.542 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'orderServiceImpl'
21:30:26.550 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'memoryMemberRepository'
21:30:26.550 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'rateDiscountPolicy'
1. @ComponentScan
@Component가 붙은 모든 클래스를 스프링 빈으로 등록한다. 이 때 스프링 빈의 기본 이름은 클래스명의 맨 앞글자만 소문자로 바꿔서 사용하거나 직접 지정하여 사용한다.
2. @Autowired 의존관계 자동 주입
생성자에 @Autowired를 지정하면 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입해준다. 이때 기본 조회 전략은 타입이 같은 빈을 찾아서 주입하는 것이다.
getBean(MemberRepository.class)처럼 타입이 같은 빈을 스프링 컨테이너에서 찾아서 주입해주는 것이다.
탐색 위치와 기본 스캔 대상
모든 자바 클래스를 다 컴포넌트 스캔하면 오래 걸리므로 필요한 위치부터 탐색하도록 시작 위치를 정해준다.
@ComponentScan(
basePackages = "hello.core",
}
- basePackages : 탐색할 패키지의 시작 위치를 지정하는 것으로 여러 위치 지정 가능
- basePAckagesClasses : 지정한 클래스의 패키지를 시작 위치로 지정. 지정하지 않으면 @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작위치로 지정됨
보통 패키지 위치를 지정하지 않고 설정 정보 클래스의 위치를 프로젝트 최상단으로 두는 방법을 사용한다. 이렇게 하면 최상단 패키지이므로 모두 자동으로 컴포넌트 스캔 대상으로 지정할 수 있고, 설정 정보 클래스가 최상단에 있으면 가시성도 올라가기 때문에 권장되는 방법이다.
스프링부트를 사용하면 @ComponentScan을 포함하고 있는 @SpringBootApplication를 프로젝트 최상단에 두는 것이 관례이다.
컴포넌트 스캔 기본 대상
@Controller, @Service, @Repository, @Configuration 전부 코드를 까보면 @Component를 포함하고 있어서 컴포넌트 스캔의 대상이 된다. 애노테이션에는 상속관계가 없는데 스프링은 이렇게 포함할 수 있도록 지원한다
@Service
보통 비즈니스 로직이 시작할 때 트랜잭션이 시작되기 때문에 @Service와 함께 애노테이션으로 트랜잭션을 시작한다.
@Repository
데이터 계층의 예외를 스프링의 추상화된 예외로 바꿔준다. 예를 들어 특정 DB에서 예외가 터지고 Service 계층까지 그 예외가 올라온 상황에 DB를 바꿔야할 경우 예외도 전부 같이 바뀌게 되어 곤란해질 수 있다.
Repository 계층의 DB를 쓰다가 다른 DB로 바꾸면 그 DB에 따라 또 다른 예외가 발생하여 Service 계층이나 다른 계층까지 전부 혼돈을 주는 것이다.
그래서 스프링이 그렇게 안되도록 예외를 추상화해서 반환해준다.
필터
- includeFilters : 컴포넌트 스캔 추가 대상 지정
- excludeFilters : 컴포넌트 스캔 제외 대상 지정
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
- 필터 적용
@MyIncludeComponent
public class BeanA {
}
@MyExcludeComponent
public class BeanB {
}
- 필터 테스트
BeanA는 등록되고 BeanB는 제외되었음을 확인할 수 있다.
FilterType 옵션
- ANNOTATION: 기본값, 애노테이션을 인식해서 동작
- ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작
- ASPECTJ: AspectJ 패턴 사용
- REGEX: 정규 표현식 사용
- CUSTOM: TypeFilter 이라는 인터페이스를 구현해서 처리
BeanA도 빼고 싶을 경우
excludeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class),
@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = BeanA.class)
}
중복 등록과 충돌
실수로 컴포넌트 스캔에서 같은 빈 이름을 등록하게 되는 경우가 있는데 이 땐 어떻게 될까?
1. 자동 빈 등록 vs 자동 빈 등록
스프링이 ConflictingBeanDefinitionException 예외를 발생시켜 준다.
2. 수동 빈 등록 vs 수동 빈 등록
수동 빈이 자동 빈을 오버라이딩 해버려서 수동 빈이 우선권을 가진다.
이렇게 중복 등록과 충돌이 생기게 되면 여러 설정들이 꼬여 큰 버그가 만들어지는 경우가 있다. 그래서 스프링부트는 수동 빈 등록과 자동 빈 등록이 충돌되면 오류가 발생하도록 해준다.
코드가 줄어들고 좋은데 애매 vs 코드가 길어지지만 명확
대부분의 경우 명확한 쪽이 낫다. 어설픈 추상화, 우선순위에서 생기는 버그는 정말 잡기 힘든 버그가 되기에 코드를 조금 더 쓰더라도 명확하게 하거나 빠르게 오류를 내도록 개발하자
'Spring > Spring 핵심 원리 - 기본' 카테고리의 다른 글
[Spring] 빈 생명주기 콜백 (0) | 2022.02.25 |
---|---|
[Spring] 의존관계 자동 주입 (0) | 2022.02.25 |
[Spring] 싱글톤 컨테이너 (0) | 2022.02.23 |
[Spring] 스프링 컨테이너와 스프링 빈 (0) | 2022.02.22 |
[Spring] 객체 지향 원리 적용 (0) | 2022.02.22 |