본문 바로가기

TIL

[2022-1-13]스프링 핵심 원리 - 기본편 : 컴포넌트 스캔

스프링 핵심 원리 기본편 : 컴포넌트 스캔

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

@Bean 으로 빈을 등록는 방식은 빈이 수십 수백개가되면 자바코드가 너무길어져 개발자의 실수가 생길 가능성이 커진다. 때문에 Spring은 컴포넌트 스캔이라는 기능을 사용해 설정파일에 등록할 빈을 작성하는 것이 아니라, 직접 빈으로 등록될 객체에 직접 @Component라는 애너테이션을 추가하여 빈으로 등록하는 기능을 제공한다. 아래는 @ComponentScan 을 사용한 Configuaration 코드이다.

@Configuration
@ComponentScan(
        basePackages = "hello.core",
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}

코드에서 볼 수 있듯이 AutoConfig 는 @Configuration 이 붙은 설정 정보 코드이지만 클래스 내부에는 어떠한 코드도 작성되어있지 않다. 다만 @ComponentScan 애너테이션을 통해 해당 설정 정보가 컴포넌트스캔을 통해 빈을 등록하겠다는 것을 명시 했을 뿐이다. 

 

이처럼 AppConfig를 구성하고 다음에는 Bean으로 등록할 클래스들에 직접 @Component라는 애너테이션을 아래와 같이 추가해 준다.

@Component
public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

    @Autowired
    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);
    }

    //테스트용
    public MemberRepository getMemberRepository() {
        return memberRepository;
    }
}

의존관계는 @Autowired 를 생성자에 붙혀주면 자동의로 주입된다. 이를 통해 만들어진 Bean의 이름은 클래스 이름에서 첫 글자를 소문자로 바꿔주고 등록한다. @Component("빈 이름") 와 같은 방식으로 임의로 빈 이름을 지정해 줄 수 있다.

 

@ComponentScan의 동작은 아래와 같다.

  1. @Component가 붙은 모든 클래스를 스프링 빈을 등록한다.
  2. @Autowired를 지정한 생성자에 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 의존관계를 주입한다.
  3. 기본적으로 같은 타입의 빈을 찾아서 주입한다. getBean() 가 작동하는 방식과 유사 

- 탐색 위치와 기본 스캔 대상

@ComponentScan는 사용된 클래스를 기준으로 본인 패키지와 하위패키지에서 @Component 를 찾는다.

옵션으로 basePackages를 지정할 수 있지만 프로젝트 최상단에 두는 것을 추천한다.

 

컴포넌트 스캔은 @Component 뿐만 아니라 @Controller, @Service, @Repository, @Configuration또한 빈에 추가하는 대상으로 인식한다. 각각 에너테이션에 정의로 이동하면 @Component를 포함하고 있다. 하지만 자바는 애노테이션 상속관계를 지원하지 않는다. 스프링이 이를 지원한다.

 

- 필터

컴포넌트 스캔은 특정 클래스, 타입등을 대상으로 추가하거나, 제외할 수 있다. 

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

includeFilters 를 통해 포함할 클래스를 추가하고, excludeFilters를 통해 스캔에서 제외할 클래스를 지정한다.

- 중복 등록과 충돌

지금까지 @ComponentScan 에 의한 자동 빈 등록, @Configuration 클래스를 작성한 수동 빈 등록에 대해 공부했다. 이에 대해 자동빈 등록을 같은 타입으로, 자동 빈 등록과 수동 빈 등록이 중복으로 이루어졌을 때에 어떤 결과가 나타나는지 공부했다. 결과는 아래와 같다.

  • 일단 자동 빈 중복 등록은 ConflictingBeanDefinitionException 예외를 발생시킨다.
  • 자동 빈 등록과 수동 빈 등록이 중복되는 타입으로 수행되면 수동 빈이 우선권을 가지고 빈으로 등록된다.(수동빈이 자동 빈을 오버라이딩 해버린다)

 

하지만 이와 같이 같은 빈을 등록하는 것은 설정들이 꼬여서 만들어지는 경우가 많고 이는 잡기 어려운 버그로 이어질 수 있다. 때문에 스프링부트 프로젝트에서는 이와같이 수동 빈과 자동빈의 중복 등록이 발생했을 경우 예외를 던지고 프로그램을 종료시킨다.

결론 및 느낀 점

스프링의 컴포넌트스캔이 어떠한 방식으로 이뤄지는지 추상적인 느낌은 잡을 수 있었던 것 같다. 앞으로 개발을 하면서 쫌더 익숙해지고 그 뒤에 정확히 어떤방식으로 동작하는지 다시 공부해보고 싶다.