Dev/Spring

[스프링 핵심 원리] ComponentScan, 의존 관계 자동주입

oxdjww 2023. 9. 26. 02:13
728x90
반응형

이전 포스팅 : [스프링 핵심 원리] Singleton Pattern of Spring Container


Intro

본 카테고리는 Inflearn 김영한 강사님의 스프링 핵심 원리 강의를 수강하며 이해하고 학습한 내용을 정리한 내용으로 구성되어 있다.

본 포스팅에서는 Spring의 @ComponentScan을 이용한 스프링 빈 등록 방법, 그리고 @Autowired를 이용한 의존 관계 자동주입에 대해 다룬다.

Previous Code (AppConfig.class)


@Configuration
// apply spring container
public class AppConfig {
    @Bean
    public MemberService memberService() {
        System.out.println("call AppConfig.memberService");
        return new MemberServiceImpl(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository() {
        System.out.println("call AppConfig.memberRepository");
        return new MemoryMemberRepository();
    }
    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
//        return new FixDiscountPolicy();
    }
}

기존의 spring container를 적용한 AppConfig class의 모습이다. @Configuration 어노테이션을 통해 설정 클래스인 AppConfig를 구현해주고, 실제로 사용할 때에는 하단과 같이 Spring Container에 AppConfig.class를 파라미터로 넣어서 사용했었다.

@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    // 1. 조회: 호출할 때 마다 같은 객체를 반환
    MemberService memberService1 = ac.getBean("memberService", MemberService.class);
    // 2. 조회: 호출할 때 마다 같은 객체를 반환
    MemberService memberService2 = ac.getBean("memberService", MemberService.class);
     // 참조값이 같음
    System.out.println("memberService1 = " + memberService1);
    System.out.println("memberService2 = " + memberService2);

    // memberService1 == memberService2
    assertThat(memberService1).isSameAs(memberService2);
}

하지만 스프링 빈이 수십, 수백개가 되면 이런 설정을 담당하는 Config class를 만들고,
내부에서는 의존 주입 및 빈 등록을 해주는 것은 개발자에게 매우매우 귀찮은 일이다.

이에, @ComponentScan , @Component , @Autowired 를 통해 이를 자동화할 수 있다.

Advanced Code (AutoAppConfig.class)


@Configuration
@ComponentScan(
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)

public class AutoAppConfig {
}

이렇게 코드를 작성하면, 이렇게 ComponentScan이 이루어진다.


  1. @ComponentScan이란 어노테이션이 붙었으므로, 프로젝트 내에 모든 class 중 @Component가 붙은 class를 찾는다.
  2. 이 때, excludeFilters에 초기화된 조건인 @Configuration 어노테이션이 붙은 class는 제외하고 찾는다.
  3. 찾은 후, @Component가 붙은 class (구체화된 클래스들) 를 스프링 빈으로 등록한다.
  4. 그리고, @Component가 붙은 class들의 생성자에서 @Autowired 어노테이션을 바탕으로 자동으로 의존 주입을 한다.
  5. @Controller, @Service, @Repository, @Configuration도 내부적으로 @Component가 붙어있기에 스캔의 대상이 된다.
  6. 그 결과, Config class를 귀찮게 만들어주지 않고도 Spring Container에 빈이 등록되고, 의존 주입까지 완료된다.

참고

FilterType 옵션 FilterType은 5가지 옵션이 있다.
ANNOTATION: 기본값, 애노테이션을 인식해서 동작한다. ex) `org.example.SomeAnnotation`
ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작한다. ex) `org.example.SomeClass`
ASPECTJ: AspectJ 패턴 사용
ex) `org.example..*Service+`
REGEX: 정규 표현식
ex) `org\.example\.Default.*`
CUSTOM: `TypeFilter` 이라는 인터페이스를 구현해서 처리 ex) `org.example.MyTypeFilter`

단, searching하는 과정에서 모든 class를 찾아보기 때문에 성능상 문제가 있을 수 있다.

그러므로,

basePackages = "hello.core.member",

라는 문구를 @ComponentScan() 괄호 안에 추가하여 어느 디렉토리에서 찾을 것인지 scope를 지정할 수 있다.

일반적으로는 Config file(이 경우에는 AutoAppConfig.class)이 위치한 디렉토리부터 쭉 내려가며 스캔한다.
그래서 프로젝트 최상단에 Config 파일을 위치시키면, 의미있는 범위 내에서 스캔을 하게 된다.
즉, test file이나 Java 기본 library등 의미없는 파일을 스캔하는 과정을 줄일 수 있다.

Bean Conflict


스프링 빈을 등록하는 과정에서, 이름의 충돌이 날 수가 있는데 이 때 두 가지 경우가 있다.

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

이 경우는 간단하다.
Spring은 ConflictingBeanDefinitionException 라는 예외를 발생시킨다.

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

이 경우는 예를 들어 설명해보겠다.

  • @Bean(name = "beanName")으로 지정해주어서 beanName으로 스프링 컨테이너에 빈이 등록된 경우
  • @Component가 붙은 class이름이 BeanName 이여서 스프링 컨테이너에 빈 이름이 beanName으로 등록된 경우

이 경우 수동으로 등록한 빈이 우선권을 가져 오버라이딩 된다.

Overriding bean definition for bean 'beanName' with a different
definition: replacing

test를 돌려보면 이런 로그와 함께 정상적으로 실행된다.

하지만 최근에 스프링 부트에서는 이런 수동 빈 등록 vs 자동 빈 등록 충돌 케이스에서 오류가 발생하도록 기본 값을 바꾸었다.

스프링 부트인 CoreApplication 클래스를 실행시키면 확인할 수 있다.


감사합니다.

728x90
반응형