스프링 부트 - 자동구성 4


    자동 구성 이해1 - 스프링 부트의 동작

    스프링 부트는 다음 경로에 있는 파일을 읽어서 스프링 부트 자동 구성으로 사용한다.

    resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

    직접 만든 memory-v2 라이브러리와 spring-boot-autoconfigure 라이브러리의 위 파일을 살펴보면 스프링 부트의 자동 구성을 확인할 수 있다. 

    // spring-boot-autoconfigure의 org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일
    
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
    org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
    ...
    • 스프링 부트는 어떤 방법으로 해당 파일들을 읽어서 자동 구성을 해주는 것일까? 
    • 스프링 부트의 자동 구성은 아래 계층구조를 바탕으로 동작하게 된다. 
    @SpringBootApplication → @EnableAutoConfiguration → @Import(AutoConfigurationImportSelector.class)

    어떻게 이렇게 동작하는 것일까?

    @SpringBootApplication
    public class ProjectV1Application {
    
        public static void main(String[] args) {
            SpringApplication.run(ProjectV1Application.class, args);
        }
    
    }
    • 먼저 위 메인 메서드가 실행된다. 이 때 SpringApplication.Run()을 할 때, ProjectV1Application.class를 기준으로 실행하는 것을 볼 수 있다. 
      • ProjectV1Application 클래스를 설정 정보로 사용한다는 뜻이다. 
    • ProjectV1Application.class는 @SpringBootApplication 어노테이션을 가지고 있다. 
    ...
    @SpringBootConfiguration
    @EnableAutoConfiguration
    ...
    public @interface SpringBootApplication {}

    @SpringBootApplication 어노테이션에는 여러 어노테이션이 붙어있다. 여기서 @EnableAutoConfiguration이라는 어노테이션이 있는데, 이 녀석 덕분에 스프링 부트는 자동 구성을 지원하게 된다. 

    ...
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    • @EnableAutoConfiguration은 @Import(AutoConfigureationImportSelect.class) 어노테이션을 가진다.
    • @Import는 주로 스프링 설정 정보(@Configuration)를 포함할 때 사용함.
    • 그런데 AutoConfigurationImportSelector.class를 열어보면 @Configuration이 아니다. 

    이것만 봐서는 이해하기가 어렵다. 정확하게 이해하려면 ImportSelector에 대해서 먼저 이해해야한다. 

     


    자동 구성 이해2 - ImportSelector

    @Import에 설정 정보를 추가하는 방법은 2가지가 있음. 

    • 정적 Import : @Import(클래스). 어떤 대상이 추가되어야 하는지 코드에 설정되어있음. 설정으로 사용할 대상을 동적으로 변경할 수 없다.
    • 동적 Import : @Import(ImportSelector) 코드로 프로그래밍해서 설정으로 사용할 대상을 동적으로 선택할 수 있음. 

     

    정적인 방법

    @Configuration
    @Import( {AConfig.class, BConfig.class} )
    public class AppConfig {...}
    • 스프링에서 다른 설정 정보를 정적으로 추가하고 싶으면 다음과 같이 @Import를 사용하면 됨. 
    • 그런데 예제처럼 AConfig, BConfig가 코드에 딱 정해진 것이 아니라 특정 조건에 따라서 설정 정보를 선택해야하는 경우 어떻게 해야할까?

     

    동적인 방법

    스프링은 설정 정보 대상을 동적으로 선택할 수 있는 ImportSelector 인터페이스를 제공한다. 

    package org.springframework.context.annotation; 
    
    public interface ImportSelector {
    	String[] selectImports(AnnotationMetadata importingClassMetadata);
    	//..
    }
    • ImportSelector는 selectImports()의 결과로 스트링 배열을 반환한다. 
      • 스트링 배열에 내가 등록하고 싶은 스프링 빈의 이름을 반환해주면, 스프링 프레임워크가 빈으로 등록해준다. 

     


    ImportSelector 직접 구현해보기.

    public class HelloImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{"hello.selector.HelloConfig"};
        }
    }
    • 설정 정보를 동적으로 선택할 수 있게 해주는 ImportSelector 인터페이스를 구현했다. 
    • 여기서는 단순히 `hello.selector.HelloConfig` 설정 정보를 반환한다.
    • 이렇게 반환된 설정 정보는 선택되어서 사용된다. 여기에 설정 정보로 사용할 클래스를 동적으로 프로그래밍 하면 됨. 

     

     

    // 일반적으로 사용하는 방법
    @Test
    void staticConfig() {
        // 이렇게 하면 스프링 컨테이너를 만들 때, HelloConfig.class를 참고함.
        AnnotationConfigApplicationContext appContext =
                new AnnotationConfigApplicationContext(StaticConfig.class);
        HelloBean bean = appContext.getBean(HelloBean.class);
        Assertions.assertThat(bean).isNotNull();
    }
    
    @Configuration
    @Import(HelloConfig.class)
    public static class StaticConfig {
    }
    • staticConfig()
      • 이 코드는 스프링 컨테이너를 만들 때, StaticConfig.class를 초기 설정 정보로 사용함. 
      • 그 결과 HelloBean이 스프링 컨테이너에 잘 등록된 것을 확인할 수 있다. 
    @Test
    void selectorConfig() {
        // 이렇게 하면 스프링 컨테이너를 만들 때, HelloConfig.class를 참고함.
        AnnotationConfigApplicationContext appContext =
                new AnnotationConfigApplicationContext(StaticConfig.class);
        HelloBean bean = appContext.getBean(HelloBean.class);
        Assertions.assertThat(bean).isNotNull();
    }
    
    @Configuration
    @Import(HelloImportSelector.class)
    public static class SelectorConfig{}
    • selectorConfig()
      • selectorConfig()는 SelectorConfig.class를 초기 설정 정보로 사용함.
      • SelectorConfig는 @Import(HelloImportSelector.class)에서 ImportSelector의 구현체인 HelloImportSelector를 사용함. 
      • 스프링은 HelloImportSelector를 실행하고, "hello.selector.HelloConfig"라는 문자를 반환받음. 
      • 스프링은 이 문자에 맞는 대상을 설정 정보로 사용한다. 즉, hello.selector.HelloConfig.class를 설정 정보로 사용함. 
      • 그 결과 HelloBean이 스프링 컨테이너에 잘 등록된 것을 확인할 수 있음. 

     


    @EnableAutoConfiguration 동작 방식

    • ImportSelector를 이해했으니 다음 코드를 이해할 수 있음. 
    // @EnableAutoConfiguration
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {...}
    • @EnableAutoConfiguration은 AutoConfigurationImportSelector.class를 @Import한다.
    • AutoConfigurationImportSelector는 ImportSelector의 구현체다. 따라서 설정 정보를 동적으로 선택할 수 있음. 
    • AutoConfigurationImportSelector는 모든 라이브러리에 있는 아래 경로의 파일을 확인한다.
      • META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
    // org.springframework.boot.autoconfigure.AutoConfiguration.imports 
    ... 
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
    org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
    org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
    org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
    ...

    그리고 파일의 내용을 읽어서 설정 정보로 선택한다. 

    • 결론은 아래 순서대로 동작한다.
      • @SpringBootApplication → @EnableAutoConfiguration → @Import(AutoConfigurationImportSelector.class) → resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일을 열어서 설정 정보 선택 
      • 해당 파일의 설정 정보를 이용해 스프링 컨테이너에 스프링 빈을 등록한다. 

     


    정리 

    스프링 부트의 자동 구성을 직접 만들어서 사용할 때는 다음을 참고하자. 

    • @AutoConfiguration에 자동 구성의 순서를 지정할 수 있다. before, after 같은 걸로 할 수 있음.
    • @AutoConfiguration도 설정 파일이다. 내부에 @Configuration이 있기 때문임.
      • 하지만 일반 스프링 설정과 라이프 사이클이 다르기 때문에 컴포넌트 스캔의 대상이 되면 안됨.
      • 파일에 지정해서 사용해야 함. 
      • resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
      • 그래스 스프링 부트가 제공하는 컴포넌트 스캔에서는 @AutoConfiguration을 제외하는 AutoConfigurationExcludeFilter가 포함되어있음. 
    // SpringBootApplication
    ...
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
          @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
    • 위의 코드를 보면, SpringBootApplication 어노테이션이 실행될 때 컴포넌트 스캔을 실행한다. 그러나 이 때 @Filter 어노테이션을 이용해 AutoConfigurationExcludeFilter.class를 실행해 AutoConfiguration.class가 컴포넌트 스캔의 대상이 되는 것을 방지함. 
    • 자동 구성이 내부에서 컴포넌트 스캔을 사용하면 안됨. 매우 중요. 대신에 자동 구성 내부에서 @Import는 사용할 수 있음. 

     


    자동 구성을 언제 사용하는가?

    • AutConfiguration은 라이브러리를 만들어서 제공할 때 사용하고, 그 외에는 사용하는 일이 거의 없다. 왜냐하면 보통 필요한 빈들은 컴포넌트 스캔하거나 직접 등록하기 때문임. 
    • 자동 구성을 알아야 하는 진짜 이유는 개발하다보면 사용하는 특정 빈들이 어떻게 등록된 것인지 확인이 필요할 때가 있음. 이럴 떄 스프링 부트의 자동 구성 코드를 읽을 수 있어야 한다. 

     


    남은 문제

    • 이런 방식으로 스프링 빈이 자동 등록되면, 빈을 등록할 때 사용하는 설정 정보는 어떻게 변경해야 하는지 의문이 들 것이다. 예를 들어서 DB 접속 URL, ID, PW 같은 것들말이다.
    • DataSource 스프링 빈을 등록할 때 이런 정보를 입력해야하는데, 어떻게 이런 정보를 동적으로 바꿀 수 있을까? 

    'Spring > Spring' 카테고리의 다른 글

    Spring : 외부 설정이란?  (0) 2023.10.21
    스프링 부트 - 자동 구성 관련 요약  (0) 2023.09.17
    스프링 부트 - 자동구성 2  (0) 2023.09.17
    스프링 부트 - 자동구성 3  (2) 2023.09.17
    스프링 부트 - 자동구성 1  (0) 2023.09.14

    댓글

    Designed by JB FACTORY