본문 바로가기
개발/스프링부트

@SpringbootApplication의 baseScanPackage 설정 이슈

by hamcheeseburger 2024. 7. 13.

이슈상황

  1. 로컬에서 코드 수정하고 springboot 구동했을 때 애플리케이션은 띄워짐
  2. 그런데 aws에 배포하니까 health 체크 실패하고 이벤트 수신도 못함
    1. Controller를 정의하여 /health api 호출 시 200응답 보내는 코드가 있음에도 health 체크 실패.
      1. AWS 설정에서 health 체크 path도 /health로 설정되어 있는 부분 확인하였음.
    2. 이벤트 수신관련한 코드도 건드린적 없음.

원인

  1. 현재 패키지가 com.example.a 이고, com.domain에 있는 컴포넌트를 의존성주입 받을 일이 있어 @SpringbootApplication(scanBasePackages={"com.domain"})을 사용
  2. 이 설정을 하고 Springboot 구동하니까 구동은 잘되는데, com.example.a 패키지에 있는 컴포넌트들이 스캔이 안되고 빈등록이 안됨

원인 분석

원인을 파악하려면 @SpringbootApplication에 대한 지식이 필요하다.

@SpringbootApplication 은 @SpringBootConfiguration , @EnableAutoConfiguration, @ComponentScan 으로 이루어져 있다.

@ComponentScan에 패키지 정보를 넣지 않으면 기본적으로 해당 클래스가 위치한 패키지를 기준으로 컴포넌트 스캔을 진행하게 되는데, 즉, @SpringbootApplication 가 적용된 파일의 패키지를 기준으로 컴포넌트 스캔이 이루어진다.

 

여기서 특이한 점은 @SpringbootApplication의 scanBasePackages 설정을 사용하거나 @SpringbootApplication 과 @ComponentScan 을 함께 사용하면 @SpringbootApplication 내부에 있는 @ComponentScan 설정이 재정의( override )된다.

 

즉, 현재 패키지가 com.example.a 라고 할 때, 아래와 같이 사용하면 com.example.a 패키지는 컴포넌트 스캔대상에 포함되지 않고, com.domain 패키지만 대상에 포함된다.

  1. @SpringbootApplication(scanBasePackages={"com.domain"})
  2. @SpringbootApplication @ComponentScan(basePackages={"com.domain"})

참고) 1번 설정에서 scanBasePackages를 살펴보면 @AliasFor 로 @ComponentScan(basePackages) 가 설정되어있는 것을 확인할 수 있다. 즉, @SpringbootApplication(scanBasePackages) 설정은 @ComponentScan(basePackages)가 추가로 붙는 것과 동일하다.

 

basePackage에 대한 내용만 override되는 것이 아니라, @SpringbootApplication 내에 있는 @ComponentScan 설정이 신규 @ComponentScan 로 대체된다는 점을 유의해야한다.

@SpringbootApplication 내에 있는 @ComponentScan 에는 excludeFilter 설정이 기본으로 되어있는데, 이 설정들도 다 제거된다고 생각해야 한다.

 

해결방법

1. @SpringbootApplication 에서 scanPackage 대상에 현재 패키지 주소도 포함한다.

- 명시적으로 현재 패키지 주소도 적어줘야 하는게 번거롭기 때문에 추천하진 않는다.

@SpringbootApplication(scanBasePackages={"com.example.a", "com.domain"})
public class MainApplication {
//,..
}

 

2. @SpringbootApplication 에서 scanPackage 정보를 명시하지 않고 별도 설정파일에서 @ComponentScan 을 활용한다.

- 이 방법은 현재 패키지 주소를 추가하지 않아도 현재 패키지와 추가한 패키지 모두 컴포넌트 스캔 대상이 된다.

  - @SpringbootApplication 의 @ComponentScan 이 동작했기 때문에 현재 패키지도 컴포넌트 스캔 대상이 포함된 것이다.

  - 즉 이 경우에는 @SpringbootApplication 의 @ComponentScan 이 override 되지 않는다.

@Configuration 
@ComponentScan(basePackages="com.example.a")
public class ServerConfiguration {
}

이런 이슈를 겪으면서, 이건 스프링부트 프레임워크에서 잘못 개발한거 아닌가? 생각을 했다. 기본값을 대체할 것이라는 생각을 사용자들이 할 수 있을까?

 

대체 왜 이렇게 동작하는건지 인터넷에서 살펴보다가 나와 같은 생각을 가진 사람이 2017년에 spring-boot 이슈항목으로 글을 올린 것을 봤다. (link)

이 분은 @SpringBootApplication에 @ComponentScan을 함께 사용하면 excludeFilters 설정이 재정의된다는 것이 이상하다는 점에서 글을 올리셨다.

 

이에 대한 어떤 분의 답변.

Boot deliberately backs off when we see the user expressing their own opinion. In this case you've expressed an opinion about the exclude filters so the defaults no longer apply. If we always kept the defaults, there'd be no way to switch them off. The approach we've taken means that you can either take complete control and remove the defaults, or you can keep them by explicitly declaring them in your excludeFilter configuration.
I think we should update the documentation to make it clearer that using @ComponentScan will replace the default exclude filters and that, if you want to keep them, you should declare them explicitly.
부트는 사용자가 자신의 의견을 표현하는 것을 보고 의도적으로 취소합니다. 이 경우(@SpringbootApplication 에 @ComponentScan 을 함께 사용한 경우) 기본값이 더 이상 적용되지 않도록 제외 필터에 대한 의견을 표현한 것입니다.
항상 기본값을 유지하면 기본값을 끌 수 있는 방법이 없습니다. 우리가 취한 접근 방식은 완전히 제어하여 기본값을 제거하거나 excludeFilter 구성에서 명시적으로 선언하여 유지할 수 있습니다.
@ComponentScan을 사용하면 기본 제외 필터가 대체되고 유지하려면 명시적으로 선언해야 한다는 점을 명확히 하기 위해 설명서를 업데이트해야 한다고 생각합니다.

 

즉, 항상 기본값이 유지되도록 동작하면 기본값을 끌 수 있는 방법이 없어지기 때문에 이러한 동작을 유지한다는 의견이다.

  • excludeFilter 관련한 유의사항은 이제 docs에 명시되어있다. (If you are using an explicit @ComponentScan directive on your @SpringBootApplication-annotated class, be aware that those filters will be disabled.)
  • basePackage와 관련한 내용도 doc에 나와있긴 한데, 내용이 좀 부족하다고 느껴짐.

요약

  1. @SpringbootApplication에 내부적으로 @ComponentScan 명시되어있음. (@ComponentScan은 package 명을 명시하지 않으면 해당 파일의 패키지 기준으로 컴포넌트 스캔을 진행)
  2. @SpringbootApplication 의 scanBasePackages 설정을 사용하거나, @SpringbootApplication 와 함께 @ComponentScan를 사용하면 내부적으로 명시된 @ComponentScan이 신규설정으로 재정의된다.
  3. 이 때문에 현재 패키지는 컴포넌트 스캔 대상이 되지 않고, basePackage로 명시한 패키지만 스캔 대상이 된다.
  4. 그러니까 @SpringbootApplication와 @ComponentScan을 함께 사용하지 말자. 별도 설정파일을 분리하여 @ComponentScan에서 추가적으로 스캔할 패키지명만 명시하면 현재 패키지와 추가할 패키지 모두 스캔 대상에 포함된다.

 

이전 댓글