본문 바로가기

Spring/스프링 핵심 원리 - 기본

06. 컴포넌트 스캔

💡 본 게시글은 김영한님의 인프런(Inflearn) 강의 스프링 핵심 원리 - 기본편에 대해 공부하고, 정리한 내용입니다.

 


1. 컴포넌트 스캔과 의존관계 자동 주입에 대한 이해 

1) 들어가며 

(1) 기존 스프링 빈 등록 방식:

  • 지금까지 스프링 빈을 등록하는 방식은 주로 `@Bean` 어노테이션을 사용한 자바 코드 또는 XML의 `<bean>` 태그를 통해 직접 설정 정보를 작성하고, 스프링 빈을 명시적으로 등록하는 방법이었습니다.

(2) 문제점:

  • 하지만 이러한 방식은 예제 수준의 간단한 애플리케이션에서는 큰 문제가 되지 않지만, 실제 복잡한 애플리케이션에서는 수십 또는 수백 개의 스프링 빈을 관리해야 하며, 이들을 일일이 설정 정보에 등록하는 것은 매우 번거로운 작업입니다. 뿐만 아니라, 이런 방식은 설정 정보가 길어지고 복잡해지며, 실수로 빈을 누락하는 등의 문제가 발생할 수 있습니다.

(3) 개발자의 반응:

  • 개발자는 보통 이런 반복적이고 번거로운 작업을 회피하려 합니다. 그래서 스프링 프레임워크는 이런 불편함을 해소하기 위해 '컴포넌트 스캔(Component Scan)'이라는 기능을 제공합니다. 이 기능을 활용하면, 개발자가 직접 설정 정보를 작성하지 않아도 스프링이 자동으로 빈을 등록해줍니다.

(4) 의존성 주입의 자동화:

  • 스프링은 의존성 주입 역시 자동으로 처리하게끔 해주는 `@Autowired` 어노테이션을 제공합니다. 이 어노테이션을 사용하면 개발자가 직접 의존성을 주입하는 코드를 작성하지 않아도, 스프링이 알아서 해당 빈에 필요한 의존성을 주입해줍니다.

(5) 결과: 

  • 이 두 기능은 스프링 개발에 있어서 중요한 부분을 차지하고 있으며, 이를 통해 개발자는 더욱 집중적으로 비즈니스 로직에만 초점을 맞출 수 있게 됩니다.

2) 예시코드를 통해 이해하기

  • 그렇다면 코드를 통해 컴포넌트 스캔과 의존관계 자동 주입을 알아보겠습니다.

 

(1) 새로운 설정 파일 생성:

  • 기존에 사용했던 `AppConfig.java`는 테스트와 과거 코드 유지를 위해 그대로 두고, 새로운 `AutoAppConfig.java` 파일을 생성합니다.

(2) AutoAppConfig.java 설정:

package hello.core;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import static org.springframework.context.annotation.ComponentScan.*;

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

(3) @ComponentScan 어노테이션 사용:

  • `@ComponentScan` 어노테이션을 사용하여 컴포넌트 스캔을 활성화합니다.
  • 이 어노테이션을 설정 정보에 붙여주면, 스프링은 해당 어노테이션이 붙은 설정 정보 클래스의 패키지를 포함해 하위 패키지까지 모두 탐색하면서 `@Component` 어노테이션이 붙은 클래스를 찾아 스프링 빈으로 등록합니다.

(4) excludeFilters 사용:

  • `excludeFilters`를 통해 `@Configuration` 어노테이션이 붙은 설정 정보는 컴포넌트 스캔 대상에서 제외하도록 설정합니다. 이는 기존 `AppConfig.java`와 같은 설정 정보도 컴포넌트 스캔의 대상이 되지 않도록 하기 위함입니다.

(5) @Bean 어노테이션 없이도 빈 관리 가능:

  • `AutoAppConfig.java`를 설정하고 나면, `@Bean` 어노테이션으로 빈을 직접 등록하는 코드를 작성하지 않아도 스프링이 알아서 빈을 관리해줍니다. 이는 컴포넌트 스캔과 의존관계 자동 주입의 강력한 기능을 보여주는 좋은 예시라고 할 수 있습니다.
참고:
 컴포넌트 스캔을 사용하면 @Configuration 이 붙은 설정 정보도 자동으로 등록되기 때문에, AppConfig, TestConfig 등 앞서 만들어두었던 설정 정보도 함께 등록되고, 실행되어 버립니다. 그래서 excludeFilters 를 이용해서 설정정보는 컴포넌트 스캔 대상에서 제외했습니다.

 보통 설정 정보를 컴포넌트 스캔 대상에서 제외하지는 않지만, 기존 예제 코드를 최대한 남기고 유지하기 위해서 이 방법을 선택했습니다.

(6) 각 클래스에 `@Component` 애노테이션 추가:

  • `@Component` 애노테이션을 각 클래스에 붙여주면, 해당 클래스가 컴포넌트 스캔의 대상이 되어 스프링 빈으로 자동 등록됩니다.
@Component
public class MemoryMemberRepository implements MemberRepository {}
@Component
public class RateDiscountPolicy implements DiscountPolicy {}

(7) `@Autowired`를 사용하여 의존성 주입:

  • `@Autowired` 애노테이션을 생성자에 붙여주면, 스프링 컨테이너가 알아서 의존성을 주입해줍니다. 이를 통해 각 클래스의 의존관계를 스프링이 자동으로 관리해주게 됩니다.
@Component
public class MemberServiceImpl implements MemberService {
     private final MemberRepository memberRepository;
     
     @Autowired
     public MemberServiceImpl(MemberRepository memberRepository) {
     	this.memberRepository = memberRepository;
     }
}

(8) 여러 의존관계를 한번에 주입:

  • `@Autowired` 애노테이션을 사용하면, 생성자에서 여러 의존관계를 한번에 주입받을 수 있습니다.
@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;
     }
}
  • 예전에는 `AppConfig`에서 `@Bean`으로 직접 설정 정보를 작성하고, 의존관계를 직접 명시했습니다.
  • 그러나 이제는 `@Component``@Autowired`를 통해 설정 정보 없이도 의존관계 주입이 가능해졌습니다.
  • 이는 스프링의 컴포넌트 스캔과 의존관계 자동 주입 기능을 통해 더욱 효율적으로 코드를 작성할 수 있다는 것을 보여줍니다.

3) 예시코드 테스트하기

  • 그렇다면 위 코드가 재대로 동작하는지 테스트를 진행해보겠습니다.

 

(1) 테스트 클래스 생성:

  • `AutoAppConfigTest.java`라는 테스트 클래스를 생성합니다.

(2) 테스트 메소드 작성:

  • `basicScan()`이라는 메소드를 작성하여 컴포넌트 스캔이 제대로 동작하는지 테스트합니다.
package hello.core.scan;

import hello.core.AutoAppConfig;
import hello.core.member.MemberService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.*;

public class AutoAppConfigTest {

     @Test
     void basicScan() {
         ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
         MemberService memberService = ac.getBean(MemberService.class);
         assertThat(memberService).isInstanceOf(MemberService.class);
     }
}

(3) `AnnotationConfigApplicationContext` 사용:

  • 스프링 컨테이너를 생성할 때 `AnnotationConfigApplicationContext`를 사용하고, 설정 정보로 `AutoAppConfig` 클래스를 넘겨줍니다.

(4) 스프링 빈 조회:

  • `ac.getBean()` 메소드를 사용하여 `MemberService` 타입의 빈을 조회합니다.

(5) 테스트 검증:

  • `assertThat()`과 `isInstanceOf()` 메소드를 사용하여 `memberService`가 `MemberService` 타입인지 확인합니다. 이를 통해 컴포넌트 스캔이 제대로 동작하여 `MemberService` 타입의 빈이 정상적으로 등록되었는지 검증합니다.

(6) 로그 분석:

  • 테스트를 실행하면 로그를 통해 컴포넌트 스캔이 잘 동작하는 것을 확인할 수 있습니다.
  • 특히 아래와 같은 로그를 통해 `RateDiscountPolicy`, `MemberServiceImpl`, `MemoryMemberRepository`, `OrderServiceImpl` 클래스가 컴포넌트 스캔의 대상이 되어 스프링 빈으로 등록되었음을 확인할 수 있습니다.
ClassPathBeanDefinitionScanner - Identified candidate component class:
.. RateDiscountPolicy.class
.. MemberServiceImpl.class
.. MemoryMemberRepository.class
.. OrderServiceImpl.class

4) 그림을 통해 컴포넌트 스캔과 자동 의존관계 주입 동작을 이해하기

  • 이제 위 코드에서 컴포넌트 스캔과 자동 의존관계 주입이 어떻게 동작하는지 그림을 통해 이해해보겠습니다.

(1) `@ComponentScan` 동작 방식

  • `@ComponentScan``@Component` 애노테이션이 붙은 모든 클래스를 스프링 빈으로 등록합니다.
  • 이 때 스프링 빈의 기본 이름은 클래스명을 사용하되, 맨 앞글자만 소문자로 변환하여 사용합니다.
    • 빈 이름 기본 전략:  `MemberServiceImpl` 클래스의 빈 이름은 `memberServiceImpl`
    • 만약 스프링 빈의 이름을 직접 지정하고 싶다면, `@Component("memberService2")`와 같이 이름을 부여할 수 있습니다.

(2) `@Autowired`를 통한 의존관계 자동 주입

  • 생성자에 `@Autowired` 애노테이션을 지정하면, 스프링 컨테이너는 자동으로 해당 스프링 빈을 찾아서 주입해줍니다.
  • 이 때 기본 조회 전략은 타입이 같은 빈을 찾아서 주입하는 것입니다. 이는 `getBean(MemberRepository.class)`를 호출하는 것과 동일하게 이해할 수 있습니다.
  • 생성자에 여러 개의 파라미터가 있어도, 스프링은 각 파라미터 타입에 맞는 빈을 찾아서 모두 자동으로 주입해줍니다.
  • 이러한 방식으로 스프링은 `@ComponentScan``@Autowired`를 통해 개발자가 직접 빈을 등록하고, 의존성을 주입하는 과정 없이도 애플리케이션의 구성 요소들을 자동으로 관리할 수 있게 도와줍니다.


2. 탐색 위치와 기본 스캔 대상 

1) 탐색할 패키지의 시작 위치 지정 

  • 스프링 빈을 자동으로 등록하는 컴포넌트 스캔 기능은 매우 효율적이지만, 너무 많은 자바 클래스를 탐색하게 되면 시간이 오래 걸릴 수 있습니다. 이를 방지하기 위해 탐색할 패키지의 시작 위치를 지정할 수 있습니다.

(1) `basePackages` 속성을 이용한 탐색 위치 지정

@ComponentScan(basePackages = "hello.core")
  • `basePackages` 속성을 사용하면 컴포넌트 스캔의 시작 위치를 지정할 수 있습니다.
  • 해당 패키지와 그 하위 패키지에서 `@Component` 어노테이션이 붙은 클래스를 찾아 스프링 빈으로 등록합니다.
  • 복수의 패키지를 지정하고 싶다면, 아래와 같이 배열 형태로 패키지 이름을 지정할 수 있습니다.
@ComponentScan(basePackages = {"hello.core", "hello.service"})

(2) `basePackageClasses` 속성을 이용한 탐색 위치 지정

  • 또 다른 방법으로 `basePackageClasses` 속성을 사용하여 지정한 클래스의 패키지를 시작 위치로 지정할 수 있습니다. 
@ComponentScan(basePackageClasses = AppConfig.class)

(3) `@ComponentScan`의 기본 동작

  • 만약 `@ComponentScan` 어노테이션이 붙은 설정 클래스에서 `basePackages`나 `basePackageClasses`를 지정하지 않은 경우, 해당 설정 클래스의 패키지가 시작 위치로 설정됩니다.

(4) 권장하는 방법

  스프링의 컴포넌트 스캔 기능을 사용할 때, 가장 권장되는 방법에 대해 설명하겠습니다. 아래의 방법은 개인적인 취향이자, 현재 스프링 부트에서 기본적으로 제공하는 방식입니다.

 

① 설정 정보 클래스의 위치를 프로젝트 최상단에 두기

  • 가장 중요한 것은 설정 정보 클래스의 위치를 프로젝트의 최상단에 두는 것입니다.
  • 예를 들어 프로젝트의 구조가 아래와 같다고 가정해봅시다:

- com.hello

- com.hello.service

- com.hello.repository

  • 이 경우, `com.hello`는 프로젝트의 시작 루트로 간주됩니다.
  • 이 위치에 메인 설정 정보인 `AppConfig`와 같은 클래스를 두고, `@ComponentScan` 애노테이션을 붙입니다. 이때 `basePackages`의 지정은 생략합니다.

 

② 컴포넌트 스캔의 대상 설정

  • 위의 설정을 통해 `com.hello`를 포함한 하위 패키지는 모두 자동으로 컴포넌트 스캔의 대상이 됩니다.
  • 프로젝트의 메인 설정 정보는 프로젝트를 대표하는 정보이므로, 프로젝트 시작 루트 위치에 두는 것이 좋습니다.

 

③ 스프링 부트의 관례 따르기

  • 스프링 부트를 사용할 경우, 스프링 부트의 대표 시작 정보인 `@SpringBootApplication` 애노테이션을 프로젝트 시작 루트 위치에 두는 것이 관례입니다.
  • 이 애노테이션 안에는 이미 `@ComponentScan`이 포함되어 있습니다. 따라서 별도로 `@ComponentScan`을 선언할 필요가 없습니다.
  • 이러한 방식을 따르면, 프로젝트의 구조를 깔끔하게 유지할 수 있으며, 스프링의 컴포넌트 스캔 기능을 최대한 활용할 수 있습니다.

 


2) 컴포넌트 스캔 기본 대상 

  • 스프링의 컴포넌트 스캔은 `@Component` 애노테이션뿐만 아니라 다른 몇 가지 애노테이션들도 기본적으로 스캔의 대상으로 포함하고 있습니다. 아래에서 이에 대해 좀 더 자세히 설명하겠습니다. 

(1)  `@Component`

  • `@Component`는 스프링에서 기본적으로 컴포넌트 스캔의 대상으로 삼는 애노테이션입니다. 이 애노테이션을 클래스에 붙이면 해당 클래스가 스프링 빈으로 자동 등록됩니다.

(2) `@Controller`

  • `@Controller`는 스프링 MVC의 컨트롤러 역할을 하는 클래스에 붙이는 애노테이션입니다.
  • `@Component`를 포함하고 있어 컴포넌트 스캔의 대상이 되며, 추가적으로 스프링 MVC 컨트롤러로서의 역할을 수행하게 됩니다. 

(3) `@Service`

  • `@Service`는 주로 서비스 계층의 클래스에 붙이는 애노테이션입니다.
  • 이 애노테이션 또한 `@Component`를 포함하고 있으므로 컴포넌트 스캔의 대상이 됩니다.
  • 비록 `@Service`가 추가적인 기능을 제공하지는 않지만, 이 애노테이션을 사용함으로써 개발자들이 해당 클래스가 비즈니스 로직을 담당하는 서비스 계층의 클래스라는 것을 쉽게 인식할 수 있습니다.

(4) `@Repository`

  • `@Repository`는 주로 데이터베이스에 접근하는 DAO(Data Access Object) 클래스에 붙이는 애노테이션입니다.
  • 이 애노테이션 역시 `@Component`를 포함하고 있어 컴포넌트 스캔의 대상이 됩니다.
  • 추가적으로, `@Repository`를 사용하는 클래스에서 발생하는 특정 예외들을 스프링의 DataAccessException으로 자동 변환해주는 기능을 제공합니다.

(5) `@Configuration`

  • `@Configuration`은 스프링의 설정 정보를 담고 있는 클래스에 붙이는 애노테이션입니다.
  • 이 애노테이션 또한 `@Component`를 포함하고 있어 컴포넌트 스캔의 대상이 됩니다.
  • 추가적으로, `@Configuration`이 붙은 클래스에서 `@Bean` 어노테이션이 붙은 메서드가 반환하는 객체를 스프링 컨테이너가 관리하는 싱글톤 객체로 등록하게 됩니다.

 

=> 이렇게 각 애노테이션은 `@Component`를 포함하고 있어 컴포넌트 스캔의 대상이 되며, 각자의 역할에 맞는 추가 기능을 제공합니다. 이를 통해 스프링은 개발자에게 편리한 기능과 명확한 역할 구분을 제공합니다.


3. 필터 

1) 스캔 대상

 스프링의 컴포넌트 스캔에서는 `includeFilters`와 `excludeFilters`를 이용해 스캔 대상을 추가하거나 제외할 수 있습니다. 아래에서 이에 대해 좀 더 자세히 설명하겠습니다.

 

(1) `includeFilters`

  • `includeFilters`는 컴포넌트 스캔 대상을 추가로 지정합니다. 예를 들어, 아래의 코드는 `MyIncludeComponent` 애노테이션을 가진 클래스를 컴포넌트 스캔 대상에 포함시키는 애노테이션입니다.
package hello.core.scan.filter;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
  • `BeanA`라는 클래스에 `@MyIncludeComponent` 애노테이션을 붙이면, 이 클래스는 컴포넌트 스캔의 대상이 됩니다. 
package hello.core.scan.filter;

@MyIncludeComponent
public class BeanA {
}

(2) `excludeFilters`

  • `excludeFilters`는 컴포넌트 스캔에서 제외할 대상을 지정합니다. 아래의 코드는 `MyExcludeComponent` 애노테이션을 가진 클래스를 컴포넌트 스캔 대상에서 제외하는 애노테이션입니다.
package hello.core.scan.filter;

import java.lang.annotation.*;

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

 

  • `BeanB`라는 클래스에 `@MyExcludeComponent` 애노테이션을 붙이면, 이 클래스는 컴포넌트 스캔의 대상에서 제외됩니다.
package hello.core.scan.filter;

@MyExcludeComponent
public class BeanB {
}

 

 

=> 이렇게 `includeFilters`와 `excludeFilters`를 사용하면, 개발자는 스프링의 컴포넌트 스캔 대상을 상세하게 조절할 수 있습니다. 이를 통해 코드의 구조를 더욱 세밀하게 관리할 수 있습니다.


(3) 설정 정보와 전체 테스트 코드

package hello.core.scan.filter;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.context.annotation.ComponentScan.Filter;

public class ComponentFilterAppConfigTest {
     @Test
     void filterScan() { 
     ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
         BeanA beanA = ac.getBean("beanA", BeanA.class);
         assertThat(beanA).isNotNull();
         
         Assertions.assertThrows(
             NoSuchBeanDefinitionException.class,
             () -> ac.getBean("beanB", BeanB.class));
     }
     
     @Configuration
     @ComponentScan(
     includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
        excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
     )
     
     static class ComponentFilterAppConfig {
     }
}
@ComponentScan(
	includeFilters = @Filter(
    	type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
		excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
	MyExcludeComponent.class)
)
  • includeFilters 에 MyIncludeComponent 애노테이션을 추가해서 BeanA가 스프링 빈에 등록됩니다.
  • excludeFilters 에 MyExcludeComponent 애노테이션을 추가해서 BeanB는 스프링 빈에 등록되지 않습니다.

2) FilterType 옵션

 스프링의 `@ComponentScan` 어노테이션에는 `FilterType`이라는 옵션이 있습니다. 이 옵션을 통해 스캔 대상을 좀 더 세밀하게 조절할 수 있습니다. `FilterType`에는 총 5가지의 옵션이 있습니다.

 

(1) ANNOTATION:

  • 이 옵션은 애노테이션을 기준으로 필터링을 수행합니다. 즉, 특정 애노테이션을 가진 컴포넌트를 스캔 대상으로 포함하거나 제외할 수 있습니다. 예를 들어, `org.example.SomeAnnotation`과 같은 애노테이션을 가진 컴포넌트를 대상으로 설정할 수 있습니다.

(2) ASSIGNABLE_TYPE:

  • 이 옵션은 특정 타입과 그 자식 타입을 기준으로 필터링을 수행합니다. 예를 들어, `org.example.SomeClass`와 그 하위 클래스를 스캔 대상으로 설정할 수 있습니다.

(3) ASPECTJ:

  • AspectJ 패턴을 사용하여 필터링을 수행합니다. AspectJ 패턴 문자열을 이용해 스캔 대상을 세밀하게 지정할 수 있습니다. 예를 들어, `org.example..*Service+`와 같이 서비스 클래스를 대상으로 설정할 수 있습니다.

(4) REGEX:

  • 정규 표현식을 사용하여 필터링을 수행합니다. 정규 표현식을 이용해 스캔 대상을 세밀하게 지정할 수 있습니다. 예를 들어, `org\.example\.Default.*`와 같은 정규 표현식을 사용하여 Default로 시작하는 모든 클래스를 스캔 대상으로 설정할 수 있습니다.

(5) CUSTOM:

  • `TypeFilter` 인터페이스를 구현하여 사용자 정의 필터링을 수행합니다. 이를 통해 더욱 복잡한 조건에 따른 스캔 대상의 설정이 가능합니다. 예를 들어, `org.example.MyTypeFilter`와 같이 사용자가 정의한 필터를 사용할 수 있습니다.

 

이러한 `FilterType` 옵션들은 `includeFilters`와 `excludeFilters`에 함께 사용됩니다. `includeFilters`는 스캔 대상에 포함시킬 컴포넌트를 지정하는 데 사용되고, `excludeFilters`는 스캔 대상에서 제외시킬 컴포넌트를 지정하는 데 사용됩니다. 

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

 

 위의 코드는 `MyIncludeComponent` 애노테이션을 가진 컴포넌트를 스캔 대상에 포함시키고, `MyExcludeComponent` 애노테이션과 `BeanA` 타입을 가진 컴포넌트를 스캔상에서 제외하는 설정을 나타냅니다.

 

 그러나 일반적으로는 `@Component` 애노테이션만으로 충분하므로, `includeFilters`를 사용할 일은 거의 없습니다. `excludeFilters`는 특정한 경우에 사용되기도 하지만 빈번하게 사용되는 옵션은 아닙니다. 특별한 이유가 없다면 스프링의 기본 설정에 따르는 것이 권장됩니다.


4. 중복 등록과 충돌 

1) 자동 빈 등록 vs 자동 빈 등록:

  • 컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록될 때, 빈의 이름이 같은 경우 스프링은 오류를 발생시킵니다. 이 때 발생하는 예외는 ConflictingBeanDefinitionException입니다.

1) 수동 빈 등록 vs 자동 빈 등록:

(1) 상황 개요:

  • 개발자가 `@Bean` 애노테이션을 사용하여 빈을 직접 등록하려고 하고, 그와 동일한 이름을 가진 빈이 컴포넌트 스캔에 의해 자동으로 등록되려는 상황

(2) 코드 예시:

@Component
public class MemoryMemberRepository implements MemberRepository {}
@Configuration
@ComponentScan( excludeFilters = @Filter(type = FilterType.ANNOTATION, 
	classes = Configuration.class)
)
public class AutoAppConfig {
 	@Bean(name = "memoryMemberRepository")
 	public MemberRepository memberRepository() {
 		return new MemoryMemberRepository();
 	}
}
  • `MemoryMemberRepository` 클래스는 `@Component` 어노테이션에 의해 자동으로 빈으로 등록되려고 하지만, `AutoAppConfig` 설정 클래스에서 수동으로 동일한 이름의 빈이 등록되므로, 수동으로 등록된 빈이 자동으로 등록하려던 빈을 오버라이딩하게 됩니다.

(3) 로깅:

  • 이런 경우, 스프링 부트는 "Overriding bean definition for bean 'memoryMemberRepository' with a different definition: replacing"와 같은 로그를 남깁니다.
  • 이는 수동으로 등록된 빈이 자동으로 등록된 빈을 오버라이딩했음을 나타냅니다.

(4) 주의점:

  • 이런 경우, 개발자가 의도적으로 이런 결과를 원하는 것이 아니라면 복잡한 버그를 초래할 수 있습니다.

(5) 스프링 부트의 대응:

  • 스프링 부트는 최근에 이러한 설정을 바꾸어, 수동 빈록과 자동 빈 등록이 충돌하면 오류를 발생시키도록 했습니다.
  • 이 때 발생하는 오류 메시지는 "Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true"입니다. 이 메시지는 빈의 이름을 바꾸거나, 빈 오버라이딩을 허용하도록 설정을 변경하라는 내용을 담고 있습니다.