- 스프링에서 객체를 스프링 컨테이너가 관리하도록 하려면 스프링 컨테이너에 등록되어야 합니다.
하지만 이러한 등록을 일일이 하는 것은 번거롭습니다.
따라서 스프링에서는 '컴포넌트 스캔'을 통해서 스프링 빈의 자동 등록을 지원합니다.
1) 컴포넌트 스캔이란?
- 컴포넌트 스캔이란 말 그대로 '컴포넌트'를 '스캔'하는 것입니다.
스프링에서 스프링 빈으로 등록하고 싶은 클래스를 @Component를 표기하면,
컴포넌트 스캔을 통해 해당 클래스를 스프링 빈으로 등록합니다.
이 때, 컴포넌트 스캔을 수행하는 클래스는 @ComponentScan이 붙어야 합니다.
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 {
}
2) 컴포넌트 스캔과 의존 관계 자동 주입
- 컴포넌트 스캔을 통해 클래스들을 스프링 빈으로 설정해주면,
스프링 빈 간의 의존 관계를 주입해줘야 합니다.
이 때, 해당 클래스의 생성자에 @Autowired를 붙이면 해당 스프링 빈을 찾아서 주입해줍니다.
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
3) 탐색 위치와 기본 스캔 대상
- 컴포넌트 스캔은 특정 위치부터 탐색을 시작하도록 지정할 수 있습니다.
@ComponentScan(
basePackages = "hello.core"
}
- 이렇게 탐색의 시작 위치를 지정하면, 이 패키지를 포함해서 하위 패키지만 탐색합니다.
-> 일반적으로 스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 @SpringBootApplication을
프로젝트의 시작 위치에 두는 것이 관례이고,
@SpringBootApplication 안에 @ComponentScan이 들어 있습니다.
4) 컴포넌트 스캔 기본 대상
- 컴포넌트 스캔은 @Component 뿐만 아니라 다음 애너테이션들도 추가로 포함합니다.
(1) @Controller: 스프링 MVC 컨트롤러에서 사용
(2) @Service: 스프링 비즈니스 로직에서 사용
(3) @Repository: 스프링 데이터 접근 계층에서 사용
(4) @Configuration: 스프링 설정 정보에서 사용
5) 필터
- 필터를 통해서 컴포넌트 스캔 대상을 추가로 지정하거나, 제외할 대상을 지정할 수 있습니다.
(1) includeFilters: 컴포넌트 스캔의 대상을 추가로 지정합니다.
(2) 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;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
컴포넌트 스캔 대상에 추가할 클래스
package hello.core.scan.filter;
@MyIncludeComponent
public class BeanA {
}
컴포넌트 스캔 대상에서 제외할 클래스
package hello.core.scan.filter;
@MyExcludeComponent
public class BeanB {
}
설정 정보와 전체 테스트 코드
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 {
}
}
6) 필터 타입 옵션
- 필터 타입에는 5가지 옵션이 있습니다.
(1) ANNOTATION: 기본값, 애노테이션을 인식해서 동작.
ex) org.example.SomeAnnotation
(2) ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작.
ex) org.example.SomeClass
(3) ASPECTJ: AspectJ 패턴 사용
ex) org.example..*Service+
(4) REGEX: 정규 표현식
ex) org\.example\.Default.*
(5) CUSTOM: TypeFilter 이라는 인터페이스를 구현해서 처리
ex) org.example.MyTypeFilter
참고
김영한 스프링 핵심 원리