Java & Spring

[Spring] 핵심원리 기본편 - 컴포넌트 스캔

컴포넌트 스캔과 의존관계 주입 개념은 연결되어 있다. 이번 강의에서는 컴포넌트 스캔에 대해 먼저 알아보자.

 

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

지금까지는 스프링 빈을 등록할 때 자바 코드의 @Bean이나 XML의 <bean> 태그를 통해서 설정 정보에 직접 등록할 스프링 빈을 나열했다. 그런데 이렇게 등록해야할 빈이 수십, 수백 개가 되면 직접 등록하는 데에 한계가 있다. 따라서 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능과 의존관계를 자동으로 주입하는 @Autowired라는 기능을 제공한다.

 

AutoAppConfig.class를 작성해보자.

@Configuration
@ComponentScan( // 컴포넌트 스캔 기능을 위해. @Component가 붙은 클래스들을 쭉 찾아서 자동으로 스프링 빈으로 등록해줌
	// 컴포넌트 스캔할 때, 뺄거를 지정해줌. 따로 만든 기존 예제 코드 AppConfig 클래스 빼주기 위해 임의로 등록
	excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
	// AppConfig와는 다르게 Bean으로 등록한게 없음
}

@ComponentScan을 설정 정보에 붙여주면 컴포넌트 스캔을 사용할 수 있다.

컴포넌트 스캔을 사용하면 @Component 애노테이션이 붙은 클래스를 자동으로 스캔해서 스프링 빈으로 등록해준다.

(이름은 기본적으로 클래스명으로 등록되고, 앞글자는 소문자로 등록된다. @Component 뒤에 ("") 안에 새로 이름을 지정해줄 수 있다.)

@Configuration도 소스 코드 열어보면 @Component가 붙어 있기 때문에 같이 스캔된다.

 

근데 이렇게 자동으로 스프링 빈에 등록이 되면 의존관계는 어떻게 주입이 될까?

생각을 해보면 우리는 지금까지 빈 등록을 해줄 때 'return new MemberServiceImpl(memberRepository());' 요런식으로 memberRepository를 등록할거야! 라고 명시를 해줬다. 근데 컴포넌트 스캔을 하면 AutoAppConfig.class에 뭐가 있는 것도 아니고..이렇게 주입을 못해주는 데 어떻게 되는 걸까.

 

여기서 자동 의존관계 주입 기능을 사용한다.

@Autowired를 생성자 위에 붙여주면 의존관계 주입이 가능해진다.

@Autowired를 붙이면 스프링 컨테이너가 자동으로 해당 스프링 빈과 타입이 같은 빈을 찾아서 주입한다.

아래 코드는 예전 강의에서 활용한 예제 코드다.

@Component
public class MemberServiceImpl implements MemberService {

	private final MemberRepository memberRepository;

	@Autowired // 자동으로 ac.getBean(MemberRepository.class)가 들어간다고 생각하자
	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);
	}
}

탐색 위치와 기본 스캔 대상

// 해당 위치와 하위 위치 스캔
@ComponentScan(basePackages = "hello.core.member")
// 시작 위치 여러개
@ComponentScan(basePackages = {"hello.core.member", "hello.service"})
// 해당 클래스의 패키지 위치부터 하위까지 스캔
// 지정하지 않으면 @ComponentScan 작성한 클래스를 디폴트로 잡음
@ComponentScan(basePackageClasses = AutoAppConfig.class)

요런식으로 basePackges 옵셕을 사용하면 탐색할 패키지의 시작 위치를 지정해줄 수 있다. (모든 자바 클래스를 다 스캔하면 시간이 오래 걸린다.)

이렇게 하면 hello.core.member 패키지를 포함해서 하위 패키지를 모두 탐색한다.

 

영한님이 권장하는 방법은 패키지 위치를 지정하지 않고(default로 두고), 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이다.

스프링 부트도 이 방법을 기본으로 제공한다. @SpringBootApplication 소스코드를 뜯어보면 @ComponentScan이 들어있는 것을 확인할 수 있다. 그리고 이 @SpringBootApplication 애노테이션은 프로젝트 시작 루트 위치에 있다.

 

컴포넌트 스캔 기본 대상은 다음과 같다.

아래 애노테이션들 소스코드를 보면 @Component를 포함하고 있는 것을 알 수 있다.

  • @Component
  • @Controller
  • @Service
  • @Repository
  • @Configuration

필터

필터 종류

  • includeFilters: 컴포넌트 스캔 대상을 추가로 지정한다.
  • excludeFilters: 컴포넌트 스캔에서 제외할 대상을 지정한다.

FilterType 종류

  • ANNOTATION: 기본값. 애노테이션을 인식해서 동작
  • ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작
  • ASPECTJ: AspectJ 패턴 사용
  • REGEX: 정규 표현식
  • CUSTOM: TypeFilter라는 인터페이스를 구현해서 처리
@ComponentScan(
    includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
    excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)

MyIncludeComponent랑 MyExcludeComponent는 직접만든 애노테이션이다.

이렇게 해주면 MyIncludeComponent 애노테이션이 붙은 클래스는 스캔 대상이 되고, MyExcludeComponent가 붙은 클래스는 스캔 대상에서 제외된다.

 

* 요즘은 개발할 때 사실 필터를 잘 사용할 일이 없다고 한다. 대부분 기본 설정을 사용한다.


중복 등록과 충돌

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

 

먼저 자동으로 빈을 등록한 것들끼리 충돌이 나면 이 경우에 스프링은 'ConflictingBeanDefinitionException' 예외를 발생시킨다.

 

자동으로 등록한 빈과 수동으로 등록한 빈끼리 충돌이 나면 수동으로 등록한 빈이 우선권을 가진다. 즉, 수동 빈이 자동 빈을 오버라이딩 해버린다. 대신 'Overriding bean definition...' 라는 로그가 남는다. 근데 이 경우는 대부분 의도적이라기보다는 개발자의 실수로 만들어지는 충돌이 대부분이다. 그래서 스프링 부트에서는 이런 충돌이 나면 오류가 발생하도록 기본 값을 변경해두었다. 스프링 부트에서는 아래와 같은 에러가 난다.

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

 


정리

이번 챕터에서는 컴포넌트 스캔에 대해서 알아봤다.

직접적으로 @ComponentScan이라고 쓸일은 많이 없지만 꼭 알아야 하는 개념이라고 생각한다.

728x90
반응형