Spring

[Spring Boot] AutoConfiguration

꾸준함. 2023. 3. 6. 18:57

개요

스프링 부트의 Bean 구성 정보는 Component Scan에 의해서 등록되는 Bean과 AutoConfiguration에 의해 자동으로 등록되는 빈으로 구분됩니다.
이번 게시글에서는 일반적으로 자동으로 등록되는 빈과 함께 AutoConfiguration의 동작 메커니즘에 대해 작성해 보겠습니다.

 

Bean의 역할과 구분

AutoConfiguration 동작 메커니즘에 대해 알아보기 전에 앞서 Bean의 정의와 함께 역할과 구분에 대해 알아야 합니다.
Bean이란 스프링 컨테이너에서 생성되고 관리되는 자바 객체이며 모두 싱글톤으로 관리됩니다.
Bean은 크게 3가지로 구분할 수 있는데 아래와 같습니다.

  • Application Logic Bean
  • Application Infrastructure Bean
  • Container Infrastructure Bean

 
Application Logic Bean

 

  • 애플리케이션의 기능, 비즈니스 로직, 도메인 로직을 담고 있는 우리가 흔히 애플리케이션을 개발한다고 할 때 개발한 코드로 만들어진 Bean
  • ex) 개발자가 개발한 Controller, Service, Repository,...

 
Application Infrastructure Bean

 

  • 대부분 개발자들이 직접 작성 X (일반적으로 AutoConfiguration에 의해 추가)
  • Bean 구성 정보에 의해 컨테이너에 등록되는 빈이지만 애플리케이션의 로직이 아니라 애플리케이션이 동작하는데 꼭 필요한 기술 기반을 제공하는 빈
  • ex) 전통적인 스프링 애플리케이션에서는 Bean으로 등록되지 않지만 Spring Boot에서 구성 정보에 의해 빈으로 등록되는 DispatcherServlet과 TomcatWebServletFactory
  • ex) DataSource, JpaEntityManagerFactory, JdbcTransactionManager,...

 
Container Infrastructure Bean

 

  • Spring Container 혹은 Spring Container가 기능을 확장하면서 추가하는 것들을 Bean으로 등록시켜서 사용하는 것
  • 개발자가 작성한 구성 정보에 의해서 생성되는 것이 아니라 Spring에서 자체적으로 추가 (Application Infrastructure Bean과의 차이점)
  • ex) DefaultAdvisorAutoProxyCreator, Environment, ApplicationContext,...

 

@Import를 통해 Component Scan 대상이 아닌 Bean 등록

@Import 애노테이션을 통해 @Component이 붙은 클래스를 빈으로 등록할 수 있으며 보통 @Configuration 애노테이션이 붙은 클래스를 가져올 수 있습니다.
앞서 언급한 Application Infrastructure Bean Configuration 클래스는 스프링 부트의 AutoConfiguration 메커니즘에 의해서 등록이 되도록 패키지 분리 작업이 선행되어야 합니다.
그리고 위와 같이 분리된 클래스는 @Import 애노테이션을 통해 포함시킵니다.
 
스프링 부트 프로젝트를 만들면 자동으로 생성되는 @SpringBootApplication을 자세히 보면 @EnableAutoConfiguration이 있는데 해당 애노테이션을 통해 AutoConfiguration 메커니즘이 동작합니다.

 
더 나아가 EnableAutoConfiguration 애노테이션을 확인하면 앞서 언급한 @Import 애노테이션이 보이는데 이를 통해 우리는 AutoConfigurationImportSelector 클래스를 통해 Application Infrastructure Bean들을 등록한다는 것을 알 수 있습니다.

 

ImportSelector 인터페이스

AutoConfigurationImportSelector 클래스를 보면 ImportSelector 인터페이스를 구현한 것을 확인할 수 있습니다.
ImportSelector 인터페이스에서 핵심이 되는 메서드는 selectImports()이며 해당 메서드가 반환하는 클래스명으로 @Configuration 클래스를 찾아서 구성 정보로 사용합니다.
 

 
ImportSelector의 구현체인 AutoConfiguraitonImportSelector의 selectImports 메서드는 아래와 같습니다.


 
메서드명을 통해 추론해 보자면 selectImports 메서드가 호출되면 아래와 같은 프로세스가 진행된다는 것을 유추할 수 있습니다.

  • 구성 정보에 추가될 후보 Configuration을 모두 불러온 뒤
  • 중복 체크를 진행하고 (유저가 등록한 구성정보가 등록된 뒤 AutoConfiguration이 적용되므로)
  • 제외할 클래스들을 제외한 뒤
  • 필터링을 한번 거치고
  • AutoConfiguration 진행

 

후보 Configuration은 아래와 같이 spring-boot-autoconfigure-x.x.x.jar 파일 하위 META-INF > spring > .imports 파일에 존재합니다.

 

 

@Configuration 클래스의 동작 방식

모든 application infrastructure bean을 스프링 부트가 AutoConfiguration을 통해 알아서 등록해 주는 것만 사용한다면 상관없지만 직접 커스텀 Configuration 클래스를 추가하고 원하는 빈을 등록하는 작업을 진행할 때가 있으므로 @Configuration 클래스의 동작 방식은 반드시 알아야 하는 내용입니다.
 
@Configuration이 붙은 클래스는 디폴트로 proxyBeanMethods 설정이 true로 설정이 됩니다.
이 경우 @Configuration이 붙은 클래스는 CGLib를 이용해 프록시 클래스로 확장을 해서 @Bean이 붙은 메서드의 동작 방식을 변경합니다.
정리하자면 factory 메서드를 통해 object를 생성하는 코드를 여러번 호출해도 싱글톤으로 관리할 수 있도록 스프링 컨테이너가 처음 시작할 때 프록시 클래스들을 생성하고 Configuration 애노테이션이 붙은 Bean Object로 사용합니다. (프록시 패턴을 적용하지 않을 경우 싱글톤으로 관리가 안됨)
 
설명이 다소 어려운 것 같아 코드로 부연 설명을 진행하겠습니다.
우선, MyConfig라는 객체를 선언하고 Bean1과 Bean2 빈이 모두 Common 빈을 의존한다고 가정하겠습니다.
이 경우 팩토리 메서드를 호출할 때마다 새로운 Bean이 생성되며 이는 Spring 동작 방식에 어긋나게 됩니다.
 

@Configuration
static class MyConfig {

    @Bean
    Common common() {
        return new Common();
    }

    @Bean
    Bean1 bean1() {
        return new Bean1(common());
    }

    @Bean
    Bean2 bean2() {
        return new Bean2(common());
    }
}

 
하지만 해당 클래스에 @Configuration 애노테이션을 붙이고 스프링 컨테이너가 관리하게 한다면 proxyBeanMethods가 디폴트로 true 값을 가지게 되므로 내부적으로 프록시 클래스를 생성하고 싱글톤으로 관리하게 됩니다.
관련 테스트 코드는 아래에서 확인이 가능합니다.


 
* 주의: 항상 proxyBeanMethods가 참일 필요는 없습니다.

  • 프록시를 생성하는 것은 비싼 동작
  • @Bean 메서드 직접 호출로 빈 의존관계를 주입하지 않는다면 굳이 프록시를 생성할 필요 없음

 

참고

인프런 토비의 스프링 부트 - 이해와 원리

반응형