스프링 핵심 원리 - 기본편 : ComponentScan

jkky98·2024년 8월 12일
0

Spring

목록 보기
26/77

AutoAppConfig

이전까지는 AppConfig 클래스에서 주로 @Bean으로 직접 빈으로 등록할 클래스의 인스턴스를 설정했다. 그러나 실제로 큰 서비스에서는 빈으로 등록해야 할 클래스가 몇 백 개에 이를 수 있다. 이로 인해 다음과 같은 문제가 발생할 수 있다:

  1. 인스턴스 누락: 많은 클래스 중 일부를 빈으로 등록하지 않을 가능성이 있다.
  2. AppConfig의 복잡성: 빈 등록 코드가 많아져 AppConfig 클래스의 관리가 어려워진다.
  3. 유지보수의 부담: 빈 관리와 의존성 설정이 번거로워진다.

스프링은 이러한 불편함을 해결하기 위해 @ComponentScan이라는 기능을 제공한다.

ComponentScan

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

@ComponentScan을 사용하면 컴포넌트 스캔을 통해 빈 등록이 자동으로 이루어진다. 위 코드에서 excludeFilters를 활용하여 특정 애노테이션을 가진 클래스의 빈 등록을 막을 수 있다. 예를 들어, 실습에서는 이전에 생성된 @Configuration이 붙은 클래스들을 빈으로 등록하지 않도록 설정했다. 이는 실습을 위해 잠시 예외적으로 처리한 경우이며, 일반적으로 @Configuration 클래스는 빈으로 등록한다.

이처럼 컴포넌트 스캔을 사용하면, 빈 등록을 위해 클래스를 AppConfig에 직접 등록할 필요 없이, 애노테이션만 추가해 등록 과정을 자동화할 수 있다. 빈 등록을 "산타가 선물을 주기 위해 직접 아이의 집으로 찾아가는 배달 시스템"에 비유할 수 있다. 빈으로 등록될 클래스는 산타의 집에 직접 갈 필요가 없으며, 산타(스프링 컨테이너)가 해당 클래스에 찾아와 등록한다는 의미다.

스캔이 제대로 작동하려면, 산타가 선물을 받을 아이의 위치를 알기 위해 아이의 머리맡에 위치 신호기를 두듯이, 빈으로 등록될 클래스에 적절한 애노테이션(@Component, @Service, @Repository 등)을 설정해 주어야 한다.

@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

위와 같이 등록될 컴포넌트에 애노테이션을 부착하고, 의존성 주입이 필요한 생성자에는 @Autowired를 추가한다. @Autowired는 결국 싱글톤 빈 객체를 자동으로 주입해야 하므로, 생성자에 주입되는 클래스들에도 @Component가 필요하다.

여기서 흥미로운 점은, OrderServiceImpl의 생성자에 @Autowired가 붙어있을 때, 주입받는 타입은 인터페이스(MemberRepository)라는 것이다. 생성자만 보면 스프링은 구현체가 무엇인지 명시적으로 알 수 없는 상태다.
그럼에도 불구하고, 컴포넌트 스캐닝을 통해 @Component가 붙은 구현체인 MemoryMemberRepository를 자동으로 찾아 주입한다. 이는 스프링이 타입 기반 의존성 주입을 수행하기 때문이다.

또 다른 구현체가 존재할 경우

만약 DBMemberRepository라는 또 다른 구현체가 빈으로 등록된다면, MemberRepository 인터페이스를 구현한 두 개의 빈(MemoryMemberRepositoryDBMemberRepository)이 존재하게 되어, 스프링은 어떤 빈을 주입해야 할지 모호해지고 NoUniqueBeanDefinitionException이 발생한다. 이러한 상황을 해결하기 위해 @Primary를 사용해 기본으로 주입될 빈을 지정하거나, @Qualifier를 통해 특정 빈을 명시적으로 선택하여 주입하는 방법이 있다. 이를 통해 빈 주입의 모호성을 해소하고 원하는 구현체를 정확히 사용할 수 있다.

@Primary

@Primary
@Component
public class MemoryMemberRepository implements MemberRepository {
    // 구현 내용
}

위와 같이 구성할 경우 같은 레벨의 다른 구현체가 있어도 기본(Primary)으로 설정된 빈 객체를 주입시킨다.

@Qualifier

@Autowired
public OrderServiceImpl(@Qualifier("memoryMemberRepository") MemberRepository memberRepository, 
                        DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}

Qualifier를 통해 주입 받을 특정 빈 객체를 선택할 수 있다. 마치 공짜로 받을 선물임에도 선물후보에 A로봇과 B로봇이 둘 다 존재한다면 A로봇을 주세요 같은 구체적인 Order가 가능한 것 이다.(물론 이렇게 구현하기 위해서는 Primary와 Qualifier를 동시에 활용해야한다.

ComponentScanning scope 설정

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

모든 파일을 스캐닝하는 것은 비효율적일 수 있으므로, 스캔 범위를 제한하기 위해 기본 경로를 설정할 수 있다(여러 개의 경로 지정도 가능하다). 기본적으로 @ComponentScan이 달린 클래스의 최상단 패키지 경로 이하를 스캐닝한다.

권장하는 방법은 패키지 위치를 별도로 지정하지 않고, AppConfig 클래스를 프로젝트 최상단 패키지에 두는 것이다. 이는 스프링 부트에서도 기본적으로 제공하는 방식으로, 스프링 부트의 디폴트를 최대한 유지하며 설정을 간소화하고 관리 효율성을 높이는 접근법이다.

Component의 Detail

컴포넌트 스캔은 @Component 뿐만 아니라 다음과 내용도 추가로 대상에 포함한다. 또한 부가 기능까지 제공한다.

  • @Component : 컴포넌트 스캔에서 사용
  • @Controller : 스프링 MVC 컨트롤러에서 사용
  • @Service : 스프링 비즈니스 로직에서 사용 , 일반 컴포넌트와 다르지 않으나 가독성을 위해 의미가 있다.
  • @Repository : 스프링 데이터 접근 계층에서 사용, 데이터 계층의 예외를 스프링 예외로 변환
  • @Configuration : 스프링 설정 정보에서 사용, 스프링 빈이 싱글톤을 유지하도록 추가 처리

중복 등록과 충돌

자동빈등록vs자동빈등록

예로 @Component로 지정한 빈 내에서 이름이 동일하다면, 예외를 발생시킨다.(ConflictingBeanDefinitionException 예외 발생)

수동 빈 등록 vs 자동 빈 등록

수동 빈 등록과 자동 빈 등록이 충돌하는 경우, 수동 빈 등록이 우선권을 가진다. 이로 인해 애플리케이션은 정상적으로 실행되지만, 스프링은 경고 로그를 출력한다.

그러나, 이러한 애매한 동작을 활용하여 로직을 작성하는 것은 좋은 방식이 아니다. 비록 내가 수동 빈 등록이 우선권을 가진다는 사실을 명확히 알고 있다고 해도, 개발은 보통 여러 명이 함께 작업하기 때문에, 다른 개발자가 이 점을 간과하거나 수동 빈 등록 객체와 자동 빈 등록 객체를 혼동하여 에러를 발생시킬 가능성이 있다.

이러한 문제를 방지하기 위해, 스프링 부트는 최근 기본 설정을 변경하여 수동 빈 등록과 자동 빈 등록이 충돌하면 오류를 발생시키도록 만들었다. 이를 확인하기 위해 CoreApplication에서 테스트를 실행하면, 충돌로 인해 테스트가 통과되지 않음을 알 수 있다.

정리

  • 컴포넌트 스캔(자동빈등록)의 장점 이해
  • 스캐닝 방식에 따른 Autowired 자동주입 필요성
  • 컴포넌트 스캔의 여러 세부 애노테이션 존재 및 부가기능
  • 중복등록의 상황은 그냥 피하자

출처 및 참고 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

profile
자바집사의 거북이 수련법

0개의 댓글