본문 바로가기
Spring

면접관 : 스프링부트 auto-configuration의 동작방식을 설명해주세요. - 1

by irerin07 2024. 2. 13.
728x90
본 포스팅에 있는 내용이 100% 정답이라고 할 수도 없고, 검색하며 읽어보고 강의를 들으면서 배운 내용을 정리한다는 목적이 더 큰 포스팅이므로 부족한 부분이 많을 수 있습니다. 언제든 댓글등으로 의견 남겨주셔서 더 좋은 포스팅이 되도록 도와주시면 감사하겠습니다.

 

면접관 : 스프링부트 auto-configuration의 동작방식을 설명해주세요. - 2

 

스프링부트로 개발을 하면서 관련 문서를 하나 둘 읽다 보면 auto-configuration이라는 말을 적어도 한 번은 보셨을 겁니다.

아니면 이 글의 제목처럼 면접을 보러 갔더니 그동안 내가 달달 외운 Dependency Injection이나 Spring Bean, 아니면 하다못해 JVM이 무엇인지는 안 물어보고 난데없이 스프링부트 auto-configuration이 무엇이고, 동작방식을 설명해 달라고 했을 때도 들어보신 경험이 있을 겁니다. 

 

auto-configuration, 직역하자면 '자동 구성'이라는 의미인데 도대체 스프링 부트가 무엇을 자동으로 구성해 준다는 것일까요?

 

스프링 부트는 왜 이런 기능을 지원하고, 이 기능으로 인해 개발자에게 어떤 혜택이 있는지, 그리고 실제로 어떻게 동작하는지 등 지금부터 하나씩 차근차근 알아보도록 하겠습니다.

 


 

우선 간단한 스프링부트 프로젝트를 하나 생성합시다.

 스프링 이니셜라이저를 사용하여 Gradle 기반의 자바 17 그리고 스프링 3.2.2 버전을 사용하는 프로젝트를 생성하도록 하겠습니다.

웹 애플리케이션을 만들 예정이니 Spring Web도 함께 추가를 해줍니다.

 

파일을 다운로드하여서 압축을 풀고 잘 작동하는지 실행을 한 번 해봅시다.

실행되어라 얍!

 

콘솔 창을 보니 별다른 이슈 없이 애플리케이션이 잘 작동하는 것을 볼 수 있습니다.

그리고 브라우저에 localhost:8080을 입력해서 들어가 보면

오류 페이지긴 하지만 일단 작동은 하네

아무것도 안 뜨는 에러 페이지지만 아무튼 잘 실행되고 있다는 것을 알 수 있습니다.

 

그런데 우리는 아무런 코드도, 설정도 하지 않았는데 뭔가 수상합니다. 

어디 한 번 콘솔창을 자세히 보니...

톰캣형이 왜 거기서 나와...?

우리는 손도 대지 않았는데 톰캣이 알아서 자기 할 일을 하고 있습니다. 그래서 우리가 localhost:8080에 접속할 수 있었고, 에러 페이지를 만날 수 있었던 것이었군요.

 

뭔가 이상하지 않은가요? 우린 그냥 실행만 시켰을 뿐인데...

왜 되는거지?

 

'이상하긴 한데 오류도 안 뜨고 잘 실행되니까 넘어가야지 ㅎㅎ' 하고 넘어갈 수도 있지만 우리가 사용하는 도구가 어떻게 작동하는지 알아두면 나중에 많은 도움이 될 겁니다. 그러니 조금 더 깊게 들어가 보도록 하겠습니다.

 


사실 auto-configuration은 스프링부트가 만들어낸 새로운 기술이 아닙니다. 이미 스프링에 있는 기술을 조금 더  효과적으로 활용할 수 있도록 스프링부트가 제공하는 기능입니다. 

 

그리고 이 auto-configuration을 잘 이해하기 위해서는 스프링부트가 어노테이션을 활용할 때 사용하는 몇 가지 기법을 알고 있어야 하는데, 왜냐하면 이 auto-configuration의 대부분이 어노테이션을 활용한 방식이기 때문입니다.

 

메타 어노테이션

메타 어노테이션이란 간단하게 말해 다른 어노테이션에 사용되는 어노테이션을 뜻합니다.

메타 어노테이션을 사용하게 되면 얻는 이점으로는 메타 어노테이션이 붙은 클래스 혹은 다른 어노테이션을 사용할 때 메타 어노테이션이 적용된 것과 기능적으로는 차이가 없다는 것입니다.  

@Component 어노테이션을 생각하시면 이해하기 쉬우실 것 같네요.

합성 어노테이션

합성 어노테이션도 메타 어노테이션을 활용한 어노테이션 활용방법입니다. 보통 메타 어노테이션을 하나 이상 적용해 만드는 경우 합성 어노테이션이라 합니다. 

@RestController를 예시로 들어 설명하자면 @RestController안에는 @Controller와 @ResponseBody라는 두 개의 어노테이션이 달려있습니다. 우리는 컨트롤러 클래스에 @Controller와 @ResponseBody 모두 사용할 수도 있지만, 이미 저 두개의 어노테이션을 사용하고 있는 @RestController를 사용함으로써 @Controller의 기능과 @ResponseBody의 기능 둘 모두를 사용할 수 있게 되는 것이죠.

 

어노테이션은 사실 이보다 조금 더 많은 내용을 담고 있지만 그건 나중에 알아보기로 하고 이제 다음으로 넘어가도록 하겠습니다.


그럼 스프링부트는 이 어노테이션들을 활용해 무슨 작업을 하는 걸까요?

우리는 스프링부트를 사용해 개발을 하면서 여러 가지 종류의 빈들을 스프링 컨테이너에 등록하게 됩니다. 

 

크게 두 종류의 빈으로 구분할 수 있는데 살펴보자면 :

  • 개발자가 어떤 빈을 사용하겠다고 명시적으로 제공한 구성정보 configuration metadata를 이용하여 스프링 컨테이너가 빈으로 등록해 주는 애플리케이션 빈
    • 이 애플리케이션 빈 역시도 크게 두 가지로 나눌 수 있습니다.
      • 애플리케이션 로직 빈 : 애플리케이션의 기능, 비즈니스 로직이나 도메인 로직을 담고 있는 빈으로 개발자가 직접 등록 합니다.
      • 애플리케이션 인프라스트럭처 빈 : 기술과 관련된 것으로 이미 만들어져있는 것을 사용하겠다고 빈 구성정보를 제공하여 사용 합니다.
  • 스프링 컨테이너 자신, 혹은 스프링 컨테이너가 기능을 확장하면서 추가해 온 것들을 개발자가 요청하지 않아도 스프링 컨테이너가 스스로 빈으로 등록하여 동작시키는 방식으로 사용되는 컨테이너 인프라스트럭처 빈 

 

이 중에서 애플리케이션 인프라스트럭처 빈을 조금 더 자세히 보도록 하겠습니다. 

 

위 그림에서 애플리케이션 로직 빈과 애플리케이션 인프라스트럭처 빈을 나누어 놓은 것을 확인할 수 있습니다.

 

사용자가 직접 등록하는 컨트롤러나 서비스등은 스프링이 ComponentScan을 통해 읽어와 빈으로 등록하고 설정을 하게 됩니다.

 

애플리케이션 인프라스트럭처 빈은 스프링 부트에서 AutoConfiguration을 통해 읽어와 빈으로 등록하고 설정을 합니다.

즉, 스프링 부트의 Auto Configuration은 이미 스프링부트에서 제공하고 있거나, 또는 외부 라이브러리를 통해 추가되는 여러 구성 정보를 스프링  부트가 자동으로 읽어와 빈으로 등록해 주고 설정해 주는 기능입니다.

이를 통해 우리는 복잡한 설정등을 크게 신경 쓰지 않고, 비즈니스 로직 구현에만 집중할 수 있게 되는 것이죠.

 

Auto Configuration

그럼 본격적으로 스프링 부트의 Auto Configuration을 알아봅시다.

스프링 부트에서 Auto Configuration을 사용하기 위해 반드시 필요한 어노테이션이 있습니다.

바로 @EnableAutoConfiguration이라는 어노테이션 입니다.

 

@EnableAutoConfiguration

@EnableAutoConfiguration을 등록함으로써 우리는 스프링 부트의 자동 설정 기능을 사용할 것이라고 명시할 수 있습니다.

그럼 해당 어노테이션의 내부 구현을 한 번 볼까요?

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

 

@AutoConfigurationPackage 그리고 @Import({AutoConfigurationImportSelector.clss}) 이 두개의 어노테이션이 뭔가 수상해 보이니 바로 확인 해보겠습니다

 

@AutoConfigurationPackage

자동 구성 패키지 정보를 저장하는 AutoConfigurationPackages 클래스의 정보를 가져오는 어노테이션입니다.

AutoConfigurationPackages 클래스는 자동 구성 패키지에 정의된 메타 어노테이션을 기반으로 정보를 가져와 이후 참조될 해당 패키지들의 이름을  별도의 리스트로 저장하는 역할을 합니다.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

AutoConfigurationPackages.Registrar.class는 저장된 패키지 정보를 BeanDefinition으로 등록해주는 역할을 합니다.

@AutoConfigurationPackage 어노테이션에서는 import해온 AutoConfigurationPackages.Registrar.class를 통해 BeanDefinition을 저장하게 되는 것입니다.

@Import({AutoConfigurationImportSelector.clss})

@Import 어노테이션에 대해 찾다보면 대부분 @Configuration이 적용된 클래스의 빈 정의를 명시적으로 컨테어너나 구성 클래스에 적용할 수 있도록 한다는 설명을 많이 찾아볼 수 있습니다.

그런데 정작 AutoConfigurationImportSelector 클래스를 들어가보면 해당 클래스에는 아무런 어노테이션이 달려 있지 않은 것을 확인할 수 있는데 어떻게 이게 가능한걸까요?

 

스프링 공식 문서에 따르면 : 

Indicates one or more @Configuration classes to import.
Provides functionality equivalent to the <import/> element in Spring XML. Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes (as of 4.2; analogous to AnnotationConfigApplicationContext.register(java.lang.Class<?>...)).

임포트할 하나 이상의 @Configuration 클래스를 나타냅니다.Spring XML의 <import/> 요소와 동등한 기능을 제공합니다. 일반 컴포넌트 클래스(4.2 버전부터, AnnotationConfigApplicationContext.register(java.lang.Class<?>...)와 유사)뿐만 아니라 @Configuration 클래스, ImportSelector 및 ImportBeanDefinitionRegistrar 구현을 가져올 수 있도록 허용합니다.

라고 합니다.

즉, 꼭 @Configuration이 붙은 클래스가 아니라도 ImportSelector라면 가져올 수 있다고 설명 되어 있습니다. 그래서 AutoConfigurationImportSelector.class를 가져올 수 있었던 것이죠.

 

이렇게 ImportSelector를 사용하는 이유는 동적으로 설정된 설정 정보를 가져오고 싶기 때문입니다.

 

동적으로 가져온다는건  AutoConfigurationImportSelector는 스프링부트에 등록되어있는 자동 구성 정보들, 즉 실제로 AutoConfiguration의 대상이 되는 클래스들을 전부 가져오는 것이 아니라 동적으로 선별하는 작업을 하게 됩니다.

그럼 자동 구성 정보는 어디에서 읽어오는 것일까요?

 

스프링 부트는 2.7버전 이전에는 META-INF\spring.factories라는 파일에 해당 정보들을 담아 두고 있엇는데 2.7버전 부터는 META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports라는 파일로 변경이 되어 AutoConfiguration.imports 파일 내부에 있는 AutoConfiguration 대상들을 선별하게 됩니다.

AutoConfiguration.imports

그럼 스프링이 시작되면서 여기 있는 모든 자동 구성 정보들이 빈으로 등록되게 되는 걸까요? 그렇진 않습니다.

아까도 언급했지만 이 중에서 선별하여 빈으로 등록하게 되는 것이죠.

 

그런데 과연 선별은 어떻게 하는 걸까요?

 

그전에 우선 애플리케이션을 다시 실행시켜 봅시다. 대신 이번에는 application.yml 혹은 application.properties에 debug 설정을 true로 맞춰두고 실행시킨 후 콘솔 창을 확인해보겠습니다.

debug : true

 

그럼 콘솔창에서 다음과 같은 내용을 확인할 수 있습니다.

============================
CONDITIONS EVALUATION REPORT
============================


Positive matches:
-----------------

   AopAutoConfiguration matched:
      - @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)

   AopAutoConfiguration.AspectJAutoProxyingConfiguration matched:
      - @ConditionalOnClass found required class 'org.aspectj.weaver.Advice' (OnClassCondition)
      
-- 생략 --

Negative matches:
-----------------

   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'jakarta.jms.ConnectionFactory' (OnClassCondition)

   AopAutoConfiguration.AspectJAutoProxyingConfiguration.JdkDynamicAutoProxyConfiguration:
      Did not match:
         - @ConditionalOnProperty (spring.aop.proxy-target-class=false) did not find property 'proxy-target-class' (OnPropertyCondition)

   AopAutoConfiguration.ClassProxyingConfiguration:
      Did not match:
         - @ConditionalOnMissingClass found unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition)

-- 생략 --

Exclusions:
-----------

    None


Unconditional classes:
----------------------

    org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration

    org.springframework.boot.actuate.autoconfigure.availability.AvailabilityHealthContributorAutoConfiguration

    org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration

    org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration

    org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

    org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration​

 

슬쩍 둘러보니 굉장히 많은 수의 AutoConfiguration 클래스들이 보입니다.

그중에 몇개는 Positive matches 항목에, 또 몇개는 Negative matches, 아니면 Exclusions와 Unconditional classes도 보이는군요.

 

이게 다 뭘까요?

 

그리고 각 클래스 앞에 보이는 @ConditionalOnClass, @ConditionalOnBean 이 어노테이션은 또 뭘까요?

 

차근차근 알아봅시다.

728x90