본문 바로가기
Spring

[Spring] 컴포넌트 스캔

by soro.k 2022. 9. 17.

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

본 포스팅은 <스프링 핵심 원리 - 기본편> 강의 내용을 복습하기 위해 간략히 작성된 글이므로 전체 내용은 위 강의에서 확인 가능합니다. 

 

1. 스프링이란

2. 객체지향의 5가지 원칙(SOLID), 관심사의 분리 

3. 제어의 역전(IoC)과 의존관계 주입(DI)

4. 스프링 컨테이너와 스프링 빈

5. 싱글톤 패턴과 싱글톤 컨테이너

6. 컴포넌트 스캔

7. @Autowired 필드명 매칭, @Qualifier, @Primary

8. 빈 생명주기 콜백

9. 빈 스코프


들어가기 전에

등록해야 될 빈이 수십, 수백 개 이상이 된다면 @Bean이나 <bean>으로 일일이 등록하기가 쉽지 않습니다. 그러다가 개발자가 실수로 누락할 수도 있고 똑같은 작업을 반복해서 해야 하기 때문에(개발자가 제일 싫어한다는) 효율성이 떨어지게 됩니다. 스프링은 이 귀찮은 작업을 개발자가 하지 않아도 되도록 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공합니다.

 

참고. 수동 빈 등록 코드

@Configuration
public class AppConfig {
	
	@Bean
	public MemberService memberService() {
		return new MemberServiceImpl(memberRepository();
	}
	
	@Bean	
	public MemberRepository memberRepository() {
		return new MemoryMemberRepository();
	}

	@Bean
	public OrderService orderService() {
		return new OrderServiceImpl(memberRepository(), discountPolicy());
	}

	@Bean
	public DiscountPolicy discountPolicy() {
		return new FixDiscountPolicy();
	}
}

 

 

컴포넌트 스캔

우선, 설정 클래스에 @Configuration@ComponentScan 애노테이션을 붙입니다.

@Configuration
@ComponentSacn
public class AutoAppConfig {
}

 

컴포넌트 스캔은 이름 그대로 @Component 애노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록하기 때문에 각 클래스가 컴포넌트 스캔의 대상이 되도록 @Component 애노테이션을 붙입니다.

@Component
public class MemoryMemberRepository implements MemberRepository {}
@Component
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    @Autowired
    public MemberServiceImpl(MemberRepository memberRepository) {
      this.memberRepository = memberRepository;
    }
}

이전에는 @Bean으로 직접 설정 정보를 작성했고 의존관계로 직접 명시했는데 이제는 그럴 수 없기 때문에 의존관계 주입도 클래스 안에서 해결해야 하는데 이때 @Autowired 애노테이션을 사용해서 의존관계를 자동으로 주입해줘야 합니다.

 

컴포넌트 스캔은 어떻게 알고 자동 의존관계를 주입해 주는 걸까요? 동작 과정은 이렇습니다.

  1. @ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록한다.
  2. 이때 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용하고 이름을 직접 지정할 수도 있다.
  3. @Autowired 의존관계 자동 주입
  4. 생성자에 해당 애노테이션을 지정하면 스프링 컨테이너가 타입이 같은 빈을 찾아서 주입한다.

 

 

MemberReposity를 구현하는 객체가 A, B 두 개라고 했을 때 내가 주입하고 싶은 객체에 @Component 애노테이션을 붙여 컴포넌트 스캔이 스프링 빈으로 등록할 수 있게 해주고 @Autowired로 해당 구현 객체를 찾아 생성자에 주입해주는 것이죠.

 

 

수동 빈 등록 vs 자동 빈 등록

지금까지 수동으로 빈을 등록하는 것과 자동으로 빈을 등록하는 방법에 대해 알아봤는데요. 최근 스프링 부트는 컴포넌트 스캔을 기본으로 사용하고 스프링 부트의 다양한 스프링 빈들도 조건이 맞으면 자동으로 등록하도록 설계했습니다. 개발자 입장에서 스프링 빈을 하나 등록할 때 @Component만 넣어주면 모든 게 해결되는데 일일이 적어주는 과정을 겪기엔 굉장히 번거롭겠죠.

 

그렇다면 수동 빈 등록은 언제 사용하는 걸까요?

애플리케이션은 업무 로직과 기술 지원 로직으로 나눌 수가 있는데요.

  • 업무 로직 빈: 웹을 지원하는 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 데이터 계층의 로직을 처리하는 리포지토리등이 모두 업무 로직이다. 보통 비즈니스 요구사항을 개발할 때 추가되거나 변경된다.
  • 기술 지원 빈: 기술적인 문제나 공통 관심사(AOP)를 처리할 때 주로 사용된다. 데이터베이스 연결이나, 공통 로그 처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술들이다.

업무 로직은 숫자도 많고 컨트롤러, 서비스, 리포지토리처럼 유사한 패턴이 있기 때문에 자동 기능을 적극적으로 사용하는 것이 좋습니다. 그래야 문제가 발생했을 때 어떤 곳에서 문제가 발생했는지 명확하게 파악하기가 좋아요.

반대로 기술 지원 로직은 업무 로직에 비해 수가 적고 적용이 잘 되고 있는지 아닌지도 파악하기 어려운 경우가 많기 때문에 가급적이면 수동 빈 등록을 사용하는 게 좋습니다.

 

 

탐색 위치와 기본 스캔 대상

탐색할 패키지의 시작 위치를 지정할 수 있는데 아래와 같이 basePackages를 지정하면 이 패키지를 포함한 하위 패키지를 모두 탐색합니다. 권장하는 방법패키지 위치를 지정하지 않고, 설정 정보 클래스 위치를 프로젝트 최상단에 두는 것입니다.

@ComponentScan(
	basePackages = "hello.core",
}

 

컴포넌트 스캔 기본 대상

- @Component : 컴포넌트 스캔에서 사용

- @Controller : 스프링 MVC 컨트롤러에서 사용

- @Service : 스프링 비즈니스 로직에서 사용

- @Repository : 스프링 데이터 접근 계층에서 사용

- @Configuration : 스프링 설정 정보에서 사용

 

컴포넌트 스캔의 용도 뿐만 아니라 위의 애노테이션들이 있으면 스프링은 부가 기능을 수행합니다.

 

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

- @Service : 특별한 처리를 하지는 않지만 개발자들이 비즈니스 계층을 인식하는데 도움이 됨

- @Repository : 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해줌

- @Configuration : 스프링 설정 정보로 인식하고 스프링 빈이 싱글톤을 유지하도록 추가 처리

 

 

필터

- includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.

- excludeFilters : 컴포넌트 스캔에서 제외할 대상 지정

제외할 객체들을 설정하기 위한 애노테이션을 만들어 줍니다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {}

제외하려는 BeanA 객체에 제외하기 위한 애노테이션을 붙입니다.

@MyExcludeComponent
public class BeanA {}
@Configuration
@ComponentScan(
	excludeFilters = @Filter(type = FilterType.ANNOTATION, 
		classes = MyExcludeComponent.class))
public class AutoAppConfig {
}

 

FilterType 옵션

- ANNOTATION : 기본값, 애너테이션을 인식해서 동작한다.

- ASSIGNABLE_TYPE : 지정한 타입과 자식 타입을 인식해서 동작한다.

- ASPECTJ : AspectJ 패턴 사용

- REGEX : 정규 표현식

- CUSTOM : TypeFilter이라는 인터페이스를 구현해서 처리