oguri's garage

Spring Boot Auto Configuration 동작 원리 본문

카테고리 없음

Spring Boot Auto Configuration 동작 원리

oguri 2025. 11. 2. 23:33


개요

Spring Boot를 처음 사용할 때 가장 신기했던 게 "어떻게 설정 파일 하나 없이 바로 실행되지?"였다.

spring-boot-starter-data-jpa만 추가하면 DataSource, EntityManager, TransactionManager가 알아서 생성됐다. 하지만 막상 커스터마이징하려니 어디서부터 손대야 할지 막막했다.


그러다 운영 중에 이상한 문제를 겪었다. 분명 내가 설정한 DataSource를 사용해야 하는데 자동 설정의 DataSource가 먼저 생성되는 것 같았다. 디버깅하다 보니 Auto Configuration의 동작 원리를 제대로 이해해야겠다는 생각이 들었다.

 

 

 




핵심 동작 원리

@EnableAutoConfiguration → spring.factories 로드 → 조건 평가 → 빈 등록 순서다.

Spring Boot 애플리케이션이 시작되면:

@SpringBootApplication  // 이 안에 @EnableAutoConfiguration이 포함됨
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

 

@SpringBootApplication에 포함된 @EnableAutoConfiguration이 활성화되면서 AutoConfigurationImportSelector가 실행된다.
이 클래스가 META-INF/spring.factories 파일(또는 AutoConfiguration.imports)을 읽어서 100개 이상의 자동 설정 클래스 목록을 가져온다.

# META-INF/spring.factories (Spring Boot 2.6 이전)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
# ... 100개 이상

 

 

 

 




조건부 설정의 핵심

자동 설정의 핵심은 조건부 등록이다. 모든 걸 무조건 등록하는 게 아니라 조건을 만족할 때만 등록한다.

주요 조건 애노테이션:

@ConditionalOnClass        // 클래스가 클래스패스에 있을 때
@ConditionalOnMissingBean  // 빈이 없을 때
@ConditionalOnProperty     // 프로퍼티가 설정되어 있을 때

DataSource 자동 설정 예시

실제 Spring Boot 내부 코드를 보면:

@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    @Configuration
    @Conditional(PooledDataSourceCondition.class)
    protected static class PooledDataSourceConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public DataSource dataSource(DataSourceProperties properties) {
            return properties.initializeDataSourceBuilder()
                .type(HikariDataSource.class)
                .build();
        }
    }
}


동작 과정:

  1. DataSource 클래스가 클래스패스에 있는가? → Yes (spring-boot-starter-data-jpa 포함)
  2. 사용자가 DataSource 빈을 등록했는가? → No
  3. HikariCP가 있는가? → Yes (기본 포함)
  4. → HikariCP DataSource 자동 생성!

 

 

 




사용자 설정이 우선하는 원리

처음에 헷갈렸던 부분이 "내가 빈을 등록하면 자동 설정이 적용 안 되나?"였다. 답은 Yes다.

// 자동 설정 사용 (아무것도 안 함)
@SpringBootApplication
public class MyApplication {
    // DataSource 자동 생성됨
}

// 커스텀 DataSource (이게 우선!)
@SpringBootApplication
public class MyApplication {

    @Bean
    public DataSource dataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        dataSource.setMaximumPoolSize(20);  // 커스텀 설정
        return dataSource;
    }

    // 자동 설정의 DataSource는 등록 안 됨!
}

 

이게 가능한 이유는 @ConditionalOnMissingBean 때문이다.

자동 설정은 "빈이 없을 때만" 등록되도록 되어 있다.

그리고 Spring은 사용자가 정의한 @Configuration을 먼저 처리하고, Auto Configuration은 나중에 처리한다.

AutoConfigurationImportSelectorDeferredImportSelector를 구현하고 있어서 일반 @Configuration보다 낮은 우선순위를 가진다.

따라서 사용자 빈이 먼저 등록되고, Auto Configuration이 실행될 때는 이미 빈이 존재해서 조건이 false가 되어 스킵된다.

 

 

 




JPA 자동 설정 과정

spring-boot-starter-data-jpa를 추가하면:

@Configuration
@ConditionalOnClass(JpaRepository.class)
@ConditionalOnMissingBean({ JpaRepositoryFactoryBean.class })
@ConditionalOnProperty(
    prefix = "spring.data.jpa.repositories",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = true
)
public class JpaRepositoriesAutoConfiguration {
    // JPA Repository 자동 활성화
}
@Configuration
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, 
                      EntityManager.class })
@AutoConfigureAfter({ DataSourceAutoConfiguration.class })
public class HibernateJpaAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            DataSource dataSource) {
        // EntityManagerFactory 자동 생성
    }

    @Bean
    @ConditionalOnMissingBean
    public PlatformTransactionManager transactionManager(
            EntityManagerFactory entityManagerFactory) {
        // TransactionManager 자동 생성
    }
}

 

동작 순서:

  1. spring-boot-starter-data-jpa 의존성 추가
  2. JpaRepository 클래스 감지
  3. DataSourceAutoConfiguration으로 DataSource 먼저 생성
  4. @AutoConfigureAfter 덕분에 DataSource 생성 후 EntityManagerFactory 생성
  5. TransactionManager 자동 생성
  6. 완료!

이렇게 순서를 보장하는 게 @AutoConfigureAfter@AutoConfigureBefore 애노테이션이다.

 

 

 




자동 설정 디버깅 방법

운영 중에 "왜 이 설정이 적용 안 되지?"라는 상황을 자주 겪었다. 이럴 때 디버그 모드를 켜면 된다.

# application.yml
debug: true

 

또는 실행 시:

java -jar app.jar --debug

 

그러면 콘솔에 이런 정보가 출력된다:

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

Positive matches: (조건 만족, 적용됨)
-----------------

DataSourceAutoConfiguration matched:
   - @ConditionalOnClass found required classes
   - @ConditionalOnMissingBean (no DataSource bean found)

JpaRepositoriesAutoConfiguration matched:
   - @ConditionalOnClass found required class (JpaRepository)
   - @ConditionalOnProperty matched

Negative matches: (조건 불만족, 적용 안 됨)
-----------------

RabbitAutoConfiguration:
   Did not match:
      - @ConditionalOnClass did not find required class (RabbitTemplate)

MongoAutoConfiguration:
   Did not match:
      - @ConditionalOnClass did not find required class (MongoClient)

 

이걸 보면 어떤 자동 설정이 활성화되었고, 왜 활성화되지 않았는지 명확히 알 수 있다.

 

 

 




자주 사용하는 커스터마이징 패턴

1. 부분 커스터마이징

전체를 다시 만들지 않고 필요한 부분만 추가한다:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    // 자동 설정은 그대로 사용하고
    // 필요한 부분만 오버라이드

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:3000");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggingInterceptor());
    }
}


2. 완전 교체

자동 설정을 쓰지 않고 완전히 새로 만든다:

@Configuration
public class CustomDataSourceConfig {

    @Bean
    public DataSource dataSource() {
        // 완전히 커스텀한 DataSource
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://prod-db:3306/mydb");
        dataSource.setUsername("user");
        dataSource.setPassword("pass");
        dataSource.setMaximumPoolSize(20);
        dataSource.setMinimumIdle(5);
        dataSource.setConnectionTimeout(30000);
        return dataSource;
    }
}


3. 프로퍼티로 제어

가장 권장하는 방법이다. 자동 설정을 그대로 쓰되 프로퍼티로 조정한다:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: user
    password: pass
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000

 

 

 

 




불필요한 자동 설정 제외하기

테스트 환경에서 DB 설정을 제외하고 싶을 때:

@SpringBootTest
@EnableAutoConfiguration(exclude = {
    DataSourceAutoConfiguration.class,
    HibernateJpaAutoConfiguration.class
})
class MyServiceTest {

    @MockBean
    private UserRepository userRepository;

    @Test
    void test() {
        // DB 없이 테스트
    }
}

 

또는 application.yml에서:

spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
      - org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration

 

 

 

 


커스텀 Auto Configuration 만들기

회사에서 공통 라이브러리를 만들 때 Auto Configuration을 만들어봤다:

// 1. 자동 설정 클래스
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MyService myService(MyProperties properties) {
        return new MyService(properties);
    }
}

// 2. Properties 클래스
@ConfigurationProperties(prefix = "my.service")
public class MyProperties {
    private String apiKey;
    private int timeout = 5000;

    // getters/setters
}

// 3. spring.factories 등록
// META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfigure.MyAutoConfiguration

 

이렇게 만들면 다른 프로젝트에서 의존성만 추가하면 자동으로 설정된다:

# application.yml
my:
  service:
    api-key: "secret-key"
    timeout: 10000
@Service
public class UserService {

    @Autowired
    private MyService myService;  // 자동 주입!

    public void doSomething() {
        myService.process();
    }
}

 

 

 

 




자주 겪은 함정

1. 조건 순서 착각

DataSource가 필요한데 아직 생성 안 됐을 때 에러가 났다. @AutoConfigureAfter로 순서를 명시해야 한다:

@Configuration
@AutoConfigureAfter({ DataSourceAutoConfiguration.class })
public class MyAutoConfiguration {

    @Bean
    public MyService myService(DataSource dataSource) {
        // DataSource가 먼저 생성되어야 함
        return new MyService(dataSource);
    }
}



2. @ConditionalOnMissingBean 누락

커스텀 Auto Configuration을 만들 때 @ConditionalOnMissingBean을 안 붙여서 사용자가 커스터마이징할 수 없었다:

// ❌ 잘못된 방법
@Bean
public MyService myService() {
    return new MyService();
}

// ✅ 올바른 방법
@Bean
@ConditionalOnMissingBean
public MyService myService() {
    return new MyService();
}



3. 성능 문제 착각

"자동 설정 클래스가 100개가 넘는데 성능 괜찮아?" 걱정했는데, 실제로는 문제없다. 조건 평가는 매우 빠르고 대부분 클래스 존재 여부만 확인한다. 실제 빈이 생성되는 건 조건을 만족하는 일부만이다.

다만 불필요한 starter를 많이 추가하면 조건 평가 자체가 늘어나므로 필요한 것만 의존성에 포함하는 게 좋다.

 

 

 




핵심 정리

Spring Boot Auto Configuration의 핵심:

  1. @EnableAutoConfiguration이 spring.factories를 읽어서 자동 설정 클래스 목록을 로드한다
  2. 조건 평가로 필요한 것만 선택적으로 등록한다
  3. @ConditionalOnMissingBean 덕분에 사용자 설정이 항상 우선한다
  4. @AutoConfigureAfter/Before로 설정 순서를 보장한다
  5. debug=true로 어떤 설정이 적용되었는지 확인할 수 있다

자동 설정을 이해하고 나니 Spring Boot가 훨씬 더 편해졌다. 필요할 때만 커스터마이징하고, 나머지는 자동 설정에 맡기면 된다. 특히 "왜 내 설정이 적용 안 되지?"라는 상황이 거의 사라졌다. 조건을 이해하면 어떤 설정이 활성화될지 예측할 수 있기 때문이다.

 

 

 




참고 자료