![[스프링 핵심 원리 - 기본편] 6. 컴포넌트 스캔](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwZQkk%2FbtsCVJzGSMy%2FS0zdmg9uJo21uKpT8cDAlK%2Fimg.png)
인프런 김영한 강사님의 스프링 핵심 원리 - 기본편을 수강하고 정리한 글입니다.
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢
www.inflearn.com
📌 컴포넌트 스캔과 의존관계 자동 주입
✅ 기존 스프링 빈 등록 방식의 단점
지금까지는 스프링 빈을 등록할 때 Java Code의 @Bean 또는, XML의 <bean>을 통해 설정 정보에 직접 등록할 스프링 빈을 나열했다.
등록할 빈이 많아질 수록 귀찮아지고, 설정 정보도 커지고, 무엇보다도 누락의 위험성이 커진다.
그렇기에 스프링은 설정 정보 없이도 자동으로 스프링 빈을 등록해주는 컴포넌트 스캔 기능을 제공해준다.
추가적으로 의존관계를 자동으로 주입해주는 @Autowired 기능도 제공한다.
✅ 컴포넌트 스캔 (Component Scan)
package hello.core;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import static org.springframework.context.annotation.ComponentScan.*;
@Configuration
@ComponentScan(
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
public class AutoAppConfig {}
먼저, @ComponentScan 을 설정 정보에 붙인다.
@Bean이 없이도 @Component 어노테이션이 붙은 클래스를 스프링이 알아서 스캔하여 스프링 빈에 등록하는 매커니즘이다.
다만 컴포넌트 스캔 사용 시, @Configuration이 붙은 설정 정보 조차도 자동으로 스프링 빈으로 등록해버린다.
따라서 필요 시, excludeFilters 옵션을 통해 설정 정보는 컴포넌트 스캔 대상에서 제외할 수 있다.
✅ @Autowired
@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;
}
}
컴포넌트 스캔 방식에서는 @Bean으로 인한 설정 정보 자체가 없기 때문에, 의존관계 주입 조차 클래스 안에서 해결해야 한다.
@Autowired는 의존관계를 자동으로 주입해준다.
✅ 컴포넌트 스캔 동작 과정
1. @ComponentScan
@ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록한다.
스프링 빈의 기본 이름은 클래스명을 그대로 사용하는데, 단 맨 앞 글자는 소문자로 변경하여 사용한다.
이름을 직접 지정하고 싶다면 @Component("memberService2")의 형태로 이름을 직접 부여할 수 있다.
2. @Autowired 의존관계 자동 주입
생성자에 @Autowired를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 조회하여 주입한다.
기본 조회 전략은 타입이 같은 빈을 찾아서 주입하는 방식이다. (getBean(MemberRepository.class)와 작동 방식이 동일하다고 보면 된다.)
생성자에 파라미터가 몇 개든, 상관 없이 다 찾아서 자동으로 주입해준다.
📌 컴포넌트 스캔 탐색 위치와 기본 스캔 대상
✅ 탐색할 패키지의 시작 위치 지정
컴포넌트 스캔 과정 시, 모든 자바 클래스를 다 스캔하면 당연히 오래걸리니, 필요 위치부터 탐색하도록 시작 위치를 지정해줄 수 있다.
@ComponentScan(
basePackages = "hello.core",
)
basePackages : 탐색할 패키지의 시작 위치 지정
basePackages에 지정된 패키지를 포함하여 그 하위 패키지 까지 모두 탐색한다.
여러개를 지정하고 싶다면,
basePackages = {"", ""}의 형태로 지정 가능하다.
basePackageClasses : 해당 클래스가 속한 패키지를 컴포넌트 스캔의 기준으로 삼는다.
클래스를 기반으로 위치를 지정할 때 사용한다.
일반적으로 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단(Root)에 작성하는 것을 추천한다.
(SpringBoot가 제공하는 기본 방법이다. @SpringBootApplication 어노테이션 내부에 @ComponentScan이 달려있다.)
📌 필터
✅ 필터
includeFilters : 컴포넌트 스캔 대상을 추가로 지정
excludeFilters : 컴포넌트 스캔 대상에서 제외
✅ 커스텀 어노테이션 활용
package hello.core.scan.filter;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
위와 같이 어노테이션을 만들 수 있다.
활용은 아래와 같이 한다.
package hello.core.scan.filter;
@MyIncludeComponent
public class BeanA {
}
설정 정보는 아래와 같다.
package hello.core.scan.filter;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.context.annotation.ComponentScan.Filter;
public class ComponentFilterAppConfigTest {
@Test
void filterScan() {
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
BeanA beanA = ac.getBean("beanA", BeanA.class);
assertThat(beanA).isNotNull();
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 {}
}
📌 중복 등록과 충돌
✅ 자동 빈 등록 vs 자동 빈 등록
컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록 될 때, 이름이 같은 경우 스프링에서 오류를 발생시킨다. (ConflictingBeanDefinitionException)
✅ 수동 빈 등록 vs 자동 빈 등록
수동 빈 등록이 우선권을 가지고, 수동 빈이 자동 빈을 오버라이딩한다.
이 경우, 에러가 발생하지는 않기 때문에 개발자가 파악하기 어렵다.
최근 SpringBoot 버전에서는 이러한 경우, 오류가 발생하도록 기본 값이 수정되어 개발자가 더욱 편리한 개발을 할 수 있다.
'Backend > Spring' 카테고리의 다른 글
[스프링 핵심 원리 - 기본편] 8. 빈 생명주기 콜백 (0) | 2024.01.02 |
---|---|
[스프링 핵심 원리 - 기본편] 7. 의존관계 자동 주입 (1) | 2024.01.02 |
[스프링 핵심 원리 - 기본편] 5. 싱글톤 컨테이너 (1) | 2024.01.01 |
[스프링 핵심 원리 - 기본편] 4. 스프링 컨테이너와 스프링 (0) | 2024.01.01 |
[스프링 핵심 원리 - 기본편] 3. 스프링 핵심 원리 이해 2 (2) | 2024.01.01 |
개발자가 되고 싶어요.