본문 바로가기
Spring

스프링 핵심 원리 기본편 - 챕터 6. 컴포넌트 스캔

by mingutistory 2023. 3. 19.
728x90

Chapter 6. 컴포넌트 스캔

컴포넌트 스캔과 의존관계 자동 주입 시작하기

AppConfig와 같이 설정 정보를 통해 빈을 등록 할 수도 있었지만 등록해야 할 스프링 빈이 수십, 수백개가 되면 관리가 귀찮고 커지게 된다

스프링에서 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공함.

 

@ComponentScan

- @Component가 붙은 클래스들을 찾아 스프링 빈으로 등록함. 

- AppConfig와 다르게 @Bean으로 클래스를 등록하지 않음.

- @Configuration이 붙은 컴포넌트도 자동으로 빈으로 모두 들어가게 됨으로 Filter로 지금은 테스트 예제들 제거 한 상태

 

@Configuration
// 기존 예제 코드를 살리기 위해서 Filter 사용해서 @Configuration 어노테이션이 붙은 걸 필터함.
@ComponentScan(
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)

 

// Bean으로 자동 등록됨
@Component
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;


	// 자동 의존성 주입을 해주기 위해서 붙여줌 - 현재 MemoryRepository가 들어가게 됨
    // MemoryMemberRepository에 @Component 어노테이션을 붙여줬기 때문에 Bean으로 등록됨. 타입 비교 후에 자동 주입 됨.
	@Autowired
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

}

 

의존 관계 주입도 해당 클래스에서 정의 해야 한다.

생성자 상단에 @Autowired를 선언해주면 생성 될 때 자동으로 스프링에서 의존성 주입을 해줌.

 

로그를 통해서 확인 할 수 있음
19:16:00.320 [Test worker] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'autoAppConfig'
19:16:00.335 [Test worker] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'rateDiscountPolicy'
19:16:00.336 [Test worker] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memberServiceImpl'
19:16:00.352 [Test worker] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memoryMemberRepository'
19:16:00.353 [Test worker] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'memberServiceImpl' via constructor to bean named 'memoryMemberRepository'
19:16:00.353 [Test worker] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'orderServiceImpl'
19:16:00.361 [Test worker] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'memoryMemberRepository'
19:16:00.361 [Test worker] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'rateDiscountPolicy'

 

@ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록함.

기본 이름: 클래스명 (앞 글자만 소문자) MemberServiceImpl -> memberServiceImpl / @Component("빈 이름")

 

@Component
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    // 스프링 컨테이너에서 타입이 같은 빈을 찾아서 주입함. getBean(MemberRepository.class)와 거의 동일함
    @Autowired
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

}

 

AutoAppConfig를 @ComponentScan으로 지정해주는 순간 @Component가 붙은 클래스들을 자동으로 빈으로 등록함.

의존 관계 자동 주입을 위해서 @Autowired 사용.

 

 

탐색 위치와 기본 스캔 대상

// basePackage: 탐색 시작 위치 패키지를 지정 할 수 있음.
@Configuration
@ComponentScan(
        basePackages = "hello.core.member",
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}

 

basePackages: 탐색할 패키지의 시작 위치 지정. (default: 지정하지 않으면 @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 됨)

basePackageClasses = 탐색할 클래스의 시작 위치 지정. 

 

권장 방법

시작 패키지 위치를 지정하지 않고 설정 정보의 클래스의 위치는 프로젝트 최상단에 둠. 

스프링 부트의 대표 시작 정보인 @SpringBootApplication과 동일한 위치에 두는 것이 관례임 - 해당 어노테이션부터 컴포넌트 스캔이 시작됨. - 부트에서는 기본적으로 사용함.

 

컴포넌트 스캔 기본 대상

@Component(컴포넌트 스캔) 뿐만 아니라 다음 내용도 추가로 대상에 포함됨.

@Controller: 스프링 MVC 컨트롤러로 인식

@Service: 스프링 비지니스 로직 (개발자들이 핵심 비즈니스 로직이 있다고 판단 할 수 있도록 표현)

@Repository: 스프링 데이터 접근 계층으로 인식 후 데이터 계층의 예외를 스프링 예외로 변환함.

@Configuration: 스프링 설정 정보, 스프링 빈을 싱글톤으로 유지함.

해당 어노테이션들은 모두 내부에 @Component를 포함하고 있음.

 

사실 애노테이션에는 상속 관계라는 것이 없어서 @Service 어노테이션에 @Component가 있다고 해서 컴포넌트 스캔 대상임을 자바 언어에서 지원하는 기능으로 알게 되는 것이 아니라, 스프링에서 지원하게 되는 기능임.

 

 

필터

IncludeFilters: 컴포넌트 스캔 대상을 추가로 지정 - @Component를 붙이면 추가 되는 거기 때문에 (기본으로 제공) 잘 사용하지 않음. inclueFilter에 넣은 클래스들에 @Component를 붙이는 것과 동일하다고 보면 됨.

 

ExcludeFilters: 컴포넌트 스캔에서 제외할 대상 지정 - 간혹 사용

 

    @Configuration
    @ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
    excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class))
    static class ComponentFilterAppConfig {

    }

 

FilterType 5가지

ANNOTATION

ASSIGNABLE

ASPECTJ

REGEX

CUSTOM

 

중복 등록과 충돌

컴포넌트 스캔에서 같은 빈 이름을 등록하게 되면?

 

1. 자동 빈 등록 vs 자동 빈 등록

컴포넌트 스캔에 의해서 자동으로 등록 될 때는 스프링에서 오류(ConflictingBeanDefinitionException

)를 터뜨림.

 

 

2. 수동 빈 등록 vs 자동 빈 등록

20:49:50.584 [Test worker] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Overriding bean definition for bean 'memoryMemberRepository' with a different definition: replacing [Generic bean: class [hello.demo.member.MemoryMemberRepository]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in file [....hello\demo\member\MemoryMemberRepository.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=autoAppConfig; factoryMethodName=memberRepository; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in hello.demo.AutoAppConfig]

 

수동 빈 등록이 우선권을 가진다.

하지만 동작 할 때 오류 나도록 기본 값이 설정되어 있음 - 명확하지 않기 때문에.

 

Q. 테스트 코드에서는 오류가 나지 않는데 CoreApplication 실행 시는 오류가 나는 이유?

basicScan() 테스트 코드의 경우 순수한 스프링 프레임워크. - new AnnotationConfigApplicationContext()

CoreApplication스프링 부트 프레임 워크를 사용해서 실행하는 것. 부트도 결국 스프링 프레임 워크여서 결과가 같아야 하지만 스프링 프레임워크의 몇가지 옵션들을 수정해서 사용하기 때문에 차이가 나게 됨.

300x250

댓글