🌱

Spring Boot Auto Configuration

최민석·2025-08-16

Spring Boot Auto Configuration

최근에 Spring 프레임워크에 대해 복기할 일이 생겼는데요, Spring Boot가 자동으로 해주던 Auto Configuration에 대한 내용이었습니다. 부끄럽게도 Component Scan에 관련해서 인식만 하고 있었지, 그 밑에 숨겨진 거대한 얼음덩어리에 대해 깊게 생각해보지 못한 것 같습니다.

그래서 본 포스팅에서는 Spring과 Spring Boot의 차이에 대해 전반적으로 정리하고, 그 중에서도 Auto Configuration이 어떻게 동작하고 어떻게 커스터마이징할 수 있는지에 대해 딥다이브 해보려 합니다.

Auto Configuration이 왜 중요한 주제일까요?

💡개발자가 '설정'보다 '기능 구현'에 집중하게 해주는 핵심 장치이기 때문입니다. 물론 개발자가 기능에만 집중할 수 있도록 매번 귀찮은 설정에 대한 부분의 책임을 프레임워크가 맡아주는 것이 좋은 전략이긴 하나, 이것에 대해서 정확히 인지하고 있지 않으면 빈이 등록된다거나, 무시되는 현상에 대해 당황하게 되고, 의도치 않은 설정 충돌 등이 발생할 수 있습니다. 또, 커스터마이징을 하기가 훨씬 어려워지고, 디버깅 난이도도 상승하게 됩니다. 무엇보다도 Spring Boot가 '자동으로 해주는 것'과 '내가 명시적으로 하는 것'의 경계를 명확히 아는 것이 가장 중요합니다.

Spring과 Spring Boot의 차이

Spring 프레임워크는 자바 진영의 사실상의 표준(De facto) 프레임워크 입니다.

  • DI(Dependency Injection)
  • IoC(Inversion of Control)
  • AOP(Aspect Oriented Programming) 과 같은 강력한 기능을 제공하지만, 기본 설정을 개발자가 직접 해주어야 한다는 특징이 있습니다.

Spring

Spring 은 마치 재료와 조리도구만 제공되는 주방과 같습니다.

  • 가스레인지, 냄비, 칼 도마는 있지만 무엇을 어떻게 조리할지는 전부 개발자의 몫입니다.
  • 예를 들어 웹 어플리케이션을 만들고 싶다면:
    • DispatcherServlet 등록
    • ViewResolver 설정
    • DataSource 생성
    • 트랜잭션 매니저 설정
    • 위 모든 걸 직접 Java ConfigXML 등으로 작성해야 합니다.
코드 예제 보기
// Java Config 예제: Spring Web MVC 설정
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example")
public class WebConfig {

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/mydb");
        ds.setUsername("user");
        ds.setPassword("password");
        return ds;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}
<!-- Maven dependency 예제 -->
<dependencies>
    <!-- Spring Web MVC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.27</version>
    </dependency>
    <!-- MySQL Connector -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
    <!-- Spring JDBC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.27</version>
    </dependency>
</dependencies>
// Gradle dependency 예제
dependencies {
    implementation 'org.springframework:spring-webmvc:5.3.27'
    implementation 'mysql:mysql-connector-java:8.0.33'
    implementation 'org.springframework:spring-jdbc:5.3.27'
}

Spring Boot

Spring Boot는 이와 달리 '기본 레시피'까지 챙겨주는 밀키트가 있는 주방입니다.

  • 자주 쓰는 메뉴(웹, JPA, 시큐리티 등)에 맞는 레시피를 기본 제공합니다.
  • 예를 들어 JPA 쓸래 라고 말하면
    • 자동으로 Hibernate를 선택하고
    • EntityManagerFactory를 생성하고
    • 트랜잭션 매니저를 등록합니다.
    • 위 모든걸 자동으로 해줍니다.
코드 예제 보기
// Gradle dependency 예제: Spring Boot JPA 스타터 추가
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.mysql:mysql-connector-j'
}
# application.yaml 예제: 데이터소스 설정
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: user
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

바로 이 과정에서 핵심 역할을 하는 것이 바로 Auto Configuration입니다. 이에 대한 내용은 다음 섹션에서 자세히 다뤄보겠습니다.

차이 요약

autoconfiguration 1

구분 Spring Spring Boot
기본 설정 전부 직접 구성 기본값 + 자동 설정 제공
서버 실행 외부 WAS 필요 내장 WAS 제공
스타터 패키지 없음 Starter 의존성으로 기능 세트 추가
빈 등록 방식 Component Scan + 수동 설정 Component Scan + Auto Configuration
목표 유연성 생산성과 빠른 시작

Auto Configuration이란 무엇인가

Spring Boot에서 Auto Configuration이란 말 그대로 자동 설정을 의미합니다.

개발자가 일일히 빈을 정의하거나 설정을 작성하지 않아도, 애플리케이션에 필요한 기본적인 구성 요소들을 자동으로 만들어주는 기능입니다.

왜 필요한가?

앞서 기술하였듯, Spring만 사용할때는 DispatcherServlet, ViewResolver, DataSource, TransactionManager 등을 개발자가 직접 일일히 등록해야 했습니다.

Auto Configuration은 이런 반복적인 작업을 제거하고, 라이브러리를 쓰면 자동으로 필요한 빈을 등록해줍니다. 즉, 개발자가 비즈니스 로직 구현에 집중하도록 돕습니다.

동작 방식

Spring Boot의 Auto Configuration은 다음과 같은 과정을 거칩니다.

  1. @EnableAutoConfiguration 활성화

    • Spring Boot 애플리케이션에 붙여진 이 어노테이션이 핵심역할을 합니다.
      • @SpringBootApplication 어노테이션에 포함되어 있습니다.
    • 내부적으로 spring.factories 파일에 정의된 여러 Auto Configuration 클래스를 로딩합니다.
  2. 조건부 등록

    • Auto Configuration 클래스들은 대부분 조건부 어노테이션을 사용합니다.
      • @ConditionalOnClass -> 특정 라이브러리가 클래스패스에 존재할 때만 빈 등록
      • @ConditionalOnMissingBean -> 이미 같은 타입의 빈이 없을 때만 등록
      • @ConditionalOnProperty -> 특정 프로퍼티 값이 존재/일치할 때만 등록
    • 이 조건 덕분에 불필요한 빈이 생성되지 않고, 개발자가 원하는 대로 커스터마이징 할 수 있습니다.
  3. 우선순위와 충돌 관리

    • ⚠️ 개발자가 직접 같은 파일의 빈을 정의하면 Auto Configuration 빈은 무시됩니다.
    • 필요하다면 spring.autoconfigure.exclude로 특정 Auto Configuration을 비활성화할 수 있습니다.

예를 들어, Spring boot에서 JPA를 사용한다고 가정해봅시다.

spring-boot-starter-data-jpa가 의존성에 존재하면:

  • DataSource
  • EntityManagerFactory
  • PlatformTransactionManager 가 자동으로 등록됩니다.

빈을 자동으로 등록해주는 로직 자체는 애초에 spring boot에 포함되어 있으며, spring-boot-starter-data-jpa 의존성이 제공하는 것은 오직 Hibernate(JPA 구현체) 등의 부가 라이브러리들 뿐입니다.

좀 더 자세히 들여다볼까요?

EntityManagerFactory의 예시를 한 번 봅시다. EntityManagerFactory는 JPA 표준에 정의된 인터페이스로, 다음과 같이 정의됩니다.

public interface EntityManagerFactory {
    EntityManager createEntityManager();
    void close();
    // 기타 메서드
}

Spring Boot에는 다음과 같이 Auto Configuration 클래스가 있습니다.

@ConditionalOnClass({ EntityManagerFactory.class, DataSource.class })
@EnableTransactionManagement
@Configuration
public class JpaBaseConfiguration { ... }

익히 알고 계시는 @Configuration 어노테이션이, @ConditionalOnClass 어노테이션과 함께 쓰였습니다.

@ConditionalOnClass({ EntityManagerFactory.class, DataSource.class })을 보면 알 수 있듯, 특정 라이브러리가 클래스패스에 존재할 때만 빈을 등록하는데요, spring-boot-starter-data-jpa 의존성을 추가하면 JPA API와 Hibernate 구현체가 클래스패스에 포함됩니다.

그 결과 JpaBaseConfiguration에 정의된 @ConditionalOnClass({ EntityManagerFactory.class, DataSource.class })조건이 충족되고, Spring Boot는 자동으로 다음 빈들을 등록합니다:

  • DataSource: application.yaml이나 application.properties에 정의된 DB 접속 정보를 기반으로 생성
  • EntityManagerFactory: Hibernate 구현체(SessionFactory)를 기반으로 JPA EntityManagerFactory 생성
  • PlatformTransactionManager: JPA 트랜잭션을 관리하는 트랜잭션 매니저 생성

🌱 저는 여기서 두 가지 궁금증이 생겼는데요, 궁금증과 해당 내용에 대해서는 아래에 첨부합니다.

왜 ConditionalOnClass는 애초에 Configuration을 포함하지 않은걸까?

@ConditionalOnClass 어노테이션은 Spring Boot에서 특정 클래스가 클래스패스에 존재할 때만 빈을 등록하도록 하는 조건을 정의합니다. 이 어노테이션이 @Configuration 같은 어노테이션을 자동으로 포함하지 않는 이유는, Spring의 설계 철학인 "유연성" 때문입니다. @ConditionalOnClass는 독립적인 조건 어노테이션으로, @Configuration뿐만 아니라 다양한 어노테이션과 함께 사용될 수 있도록 의도적으로 분리되어 있습니다. 만약 자동으로 @Configuration을 포함한다면, 조건을 메서드 수준이나 다른 컴포넌트에 적용할 때 불필요한 제약이 생길 수 있습니다. 따라서 매번 함께 지정해야 하는 것은 맞지만, 이는 개발자가 조건을 더 세밀하게 제어할 수 있게 해줍니다.

예를 들어, 다음과 같이 메서드 수준에서 적용하거나, @Service 등의 다른 어노테이션에서도 사용가능합니다.

@Configuration
public class MyConfig {
    @Bean
    @ConditionalOnClass(name = "com.example.MyOptionalClass")
    public MyBean myBean() {
        return new MyBean();
    }
}

스프링 부트 개발자들은 어떻게 존재하지도 않는 클래스를 포함해서 빌드를 할 수 있었는가?

스프링 부트에 포함된 아래 클래스를 살펴보겠습니다.

import ...(생략)
...
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;

@AutoConfiguration(
		after = { DataSourceAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class },
		before = { TransactionAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class })
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class, SessionImplementor.class })
@EnableConfigurationProperties(JpaProperties.class)
@Import(HibernateJpaConfiguration.class)
public class HibernateJpaAutoConfiguration {
}

위 클래스는 Spring Boot 자체에 포함되어 있는데요, 보시는 바와 같이 LocalContainerEntityManagerFactoryBean.class가 있는 경우에만 트리거됩니다. 그런데 여기서 의문이 들었습니다. "아니, LocalContainerEntityManagerFactoryBean.class가 이미 있으니까 import 가능했던거 아니야? 그럼 클래스 패스에 무조건 항상 있다는게 보장되는거 아닌가?"

실제로import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;를 보면 import 하고 있다는걸 알 수 있죠. 여기서 스프링 부트를 사용하는 프로젝트의 org.springframework.orm관련 의존성을 제거하면, Intellij에서는 다음과 같이 빨간색으로 나옵니다. 해당 클래스가 없으니깐요!

autoconfiguration

스프링 부트의 HibernateJpaAutoConfiguration 클래스에서 LocalContainerEntityManagerFactoryBean.class를 직접 import하고 @ConditionalOnClass로 참조할 수 있는 이유는, 스프링 부트 프로젝트의 빌드 시스템(특히 Maven POM 설정)에서 "optional dependency" 메커니즘을 활용하기 때문입니다. 이는 컴파일 타임과 런타임 의존성을 분리하여 유연성을 제공하는 설계입니다. 아래에서 논리적으로 단계별로 설명하겠습니다.

기본 개념: Maven의 Optional Dependency
Maven에서 의존성을 선언할 때 <optional>true</optional> 태그를 사용하면, 해당 의존성은 다음과 같이 동작합니다:

  • 컴파일 타임(빌드 시): Maven 빌드 과정에서 해당 JAR(예: spring-orm.jar)를 다운로드하고 클래스패스에 추가합니다. 따라서 소스 코드에서 해당 클래스를 import하거나 .class로 참조할 때 컴파일 에러가 발생하지 않습니다.
  • 런타임 및 Transitive(전이) 의존성: 이 의존성은 프로젝트의 최종 아티팩트(JAR)에 포함되지 않으며, 이 프로젝트를 사용하는 다른 프로젝트(예: 사용자 애플리케이션)로 자동 전파되지 않습니다. 즉, 선택적(optional)으로, 사용자가 명시적으로 추가해야만 클래스패스에 들어옵니다.
  • 이는 스프링 부트의 "조건적 자동 구성(conditional auto-configuration)" 철학을 지원합니다. 특정 라이브러리가 클래스패스에 없을 때는 해당 구성을 무시하지만, 빌드 시에는 코드가 문제없이 컴파일되어야 합니다.

Auto Configuration의 동작 원리

auto configuration 1

Spring Boot의 Auto Configuration은 크게
초기화 -> 후보 로딩 -> 조건 평가 -> 빈 등록 이라는 흐름으로 동작합니다.

1. 초기화 단계-@EnableAutoConfiguration

  • @SpringBootApplication 내부에는 @EnableAutoConfiguration이 포함되어 있습니다.
  • 이 어노테이션이 SpringFactoriesLoader를 이용해 META-INF/spring.factories 또는 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일에 정의된 클래스 목록을 읽어옵니다.
  • 읽어온 클래스들은 전부 @Configuration이 붙어있어 스프링 설정 클래스로 취급됩니다.

2. 후보 로딩 단계-후보 메모리 적재

  • 이 시점에서 모든 Auto Configuration 클래스는 단순히 후보 목록일 뿐, 빈으로 등록된 것은 아닙니다.
  • 목록에 오른 모든 설정 클래스는 조건부 어노테이션(@ConditionalOnXXX)로 활성화 여부를 결정합니다.

3. 조건 평가 단계-@Conditional

  • @ConditionalOnClass -> 특정 라이브러리가 클래스패스에 존재할 때만 빈 등록
  • @ConditionalOnMissingBean -> 이미 같은 타입의 빈이 없을 때만 등록
  • @ConditionalOnProperty -> 특정 프로퍼티 값이 존재/일치할 때만 등록

등 다양한 조건을 검사하여 컨텍스트에 등록할지 말지를 결정합니다.

4. 빈 등록 단계

  • 조건을 통과한 Auto Configuration 클래스만 스프링 컨텍스트에 등록됩니다.
  • 해당 클래스의 @Bean 메서드들이 실행되어 필요한 빈들이 생성됩니다.
  • ⚠️이때, 사용자가 동일 타입의 빈을 명시적으로 정의하면 Auto Configuration의 해당 빈은 동작하지 않습니다!

5. 우선순위와 충돌 방지

  • Auto Configuration 클래스 간에도 적용 순서를 지정할 수 있습니다.
    • @AutoConfiguration(before = ...), @AutoConfiguration(after = ...) 로 순서 조정이 가능합니다.
  • 개발자는 spring.autoconfigure.exclude 또는 @EnableAutoConfiguration(exclude = ...)로 특정 Auto Configuration을 비활성화할 수 있습니다.

application.yaml, Auto Configuration의 연결

Spring Boot에서는 application.yaml 또는 application.properties 파일에 정의된 설정 값들이 자동으로 자바 객체에 바인딩됩니다.
이 바인딩은 @ConfigurationProperties 어노테이션이 붙은 클래스들을 통해 이루어집니다.

예를 들어, 다음과 같은 application.yaml 설정이 있다고 가정해봅시다:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: user
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

이 설정은 내부적으로 다음과 같은 자바 클래스에 매핑됩니다:

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {
    private String url;
    private String username;
    private String password;
    private String driverClassName;
    // getters and setters
}

@ConfigurationProperties(prefix = "spring.jpa")
public class JpaProperties {
    private Hibernate hibernate = new Hibernate();
    private boolean showSql;
    // getters and setters

    public static class Hibernate {
        private String ddlAuto;
        // getters and setters
    }
}

Auto Configuration 클래스들은 이러한 @ConfigurationProperties 객체들을 @EnableConfigurationProperties를 통해 주입받아, 설정된 값들을 기반으로 빈을 생성하거나 동작을 조절합니다.
예를 들어, DataSourceAutoConfigurationDataSourceProperties를 참고하여 데이터베이스 연결을 위한 DataSource 빈을 생성합니다.

이처럼 application.yaml의 설정은 Auto Configuration이 적절한 빈을 구성할 수 있는 중요한 입력값 역할을 하며, 개발자는 설정 파일만 수정하여 애플리케이션의 동작 방식을 쉽게 바꿀 수 있습니다.

Auto Configuration 구현 과정

Spring Boot의 강력한 자동 설정 기능은 스타터(Starter) 의존성과 Auto Configuration 메타데이터의 조합으로 구현됩니다.

  • 스타터 의존성(spring-boot-starter-xxx)은 실제 라이브러리와 함께 해당 라이브러리를 위한 Auto Configuration 클래스를 포함합니다.
  • 이 Auto Configuration 클래스들은 META-INF/spring.factories (Spring Boot 2.x) 또는 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (Spring Boot 3.x 이상) 파일에 등록되어 있습니다.
  • 이러한 메타데이터 파일을 통해 Spring Boot는 어떤 Auto Configuration 클래스를 로드할지 결정합니다.

예를 들어, spring-boot-starter-data-jpa 의존성은 다음을 포함합니다:

  • JPA API 및 Hibernate 구현체 라이브러리
  • JPA 관련 Auto Configuration 클래스 (예: HibernateJpaAutoConfiguration)
  • META-INF/spring.factories 또는 AutoConfiguration.imports에 Auto Configuration 클래스 등록

이로 인해 개발자가 별도로 설정하지 않아도 JPA와 Hibernate가 자동으로 설정되고, 필요한 빈이 생성됩니다.

또한, Auto Configuration 클래스들은 보통 @ConditionalOnClass 같은 조건부 어노테이션을 사용하여, 실제 라이브러리가 클래스패스에 존재할 때만 활성화됩니다.
이 덕분에 불필요한 설정이 적용되지 않고, 선택적으로 기능이 활성화됩니다.

또한, 스타터 의존성은 필요한 라이브러리들을 "optional dependency"로 포함하기도 합니다.
이는 컴파일 타임에는 라이브러리가 포함되어 있지만, 런타임에서는 개발자가 직접 추가하지 않으면 클래스패스에 없을 수도 있다는 의미입니다.
이런 설계는 Auto Configuration의 조건 평가와 맞물려, 유연한 구성과 빌드 환경을 가능하게 합니다.

마지막으로, 개발자가 직접 만든 라이브러리도 Auto Configuration을 제공할 수 있습니다.
이를 위해서는:

  • 자동 설정용 @Configuration 클래스를 작성하고,
  • META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일에 해당 클래스를 등록합니다.

이렇게 하면 사용자가 라이브러리를 의존성에 추가하는 것만으로도 자동 설정 기능을 제공할 수 있습니다.

요약하면, Spring Boot의 자동 설정은 스타터 의존성과 Auto Configuration 메타데이터가 유기적으로 결합되어 작동하며, 이를 통해 개발자는 최소한의 설정으로 복잡한 환경을 쉽게 구성할 수 있습니다.

Auto Configuration 커스터마이징

Spring Boot의 자동 구성은 기본값을 제공하되, 내가 원하면 언제든 바꿀 수 있게 설계되어 있습니다. 대표적인 커스터마이징 방법을 정리합니다.

1. 동일 타입 빈을 정의해 오버라이드하기

Auto Configuration은 보통 @ConditionalOnMissingBean을 사용합니다. 즉 내가 같은 타입의 빈을 정의하면 자동 구성은 무시됩니다..

@Configuration
public class MyJpaTuningConfig {
    // 기본 JpaVendorAdapter 대신 커스텀 제공
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setShowSql(true);
        adapter.setGenerateDdl(false);
        return adapter;
    }
}

참고: @ConditionalOnMissingBean은 자동 구성에서 매우 흔하게 쓰이며, 사용자가 원하는 빈을 정의하면 자동 구성이 백오프(back off)됩니다.

2. 특정 Auto Configuration 비활성화하기

전역 또는 특정 구성에서 자동 구성을 제외(exclude) 할 수 있습니다.

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class App {}

또는 application.yaml로 제어합니다.

spring:
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

애노테이션의 exclude/excludeName, 그리고 프로퍼티 spring.autoconfigure.exclude로 제어할 수 있습니다.

3. 프로퍼티로 동작 바꾸기

많은 자동 구성은 @ConfigurationProperties와 연계되어 프로퍼티만 바꿔도 동작을 변경할 수 있습니다. (예: spring.jpa.*, spring.datasource.*)

4. 로딩 순서 조정하기

자동 구성 간 의존이 있으면 @AutoConfiguration(before=..., after=...)적용 순서를 명시합니다.

5. 상태 확인/디버깅

  • 실행 시 --debug 또는 debug=trueConditions Evaluation Report를 확인합니다.
    java -jar app.jar --debug
    
    _어떤 자동 구성이 왜(조건 충족/불충족) 적용되었는지를 한눈에 볼 수 있습니다.
  • Actuator의 /actuator/conditions 엔드포인트로도 확인 가능합니다.

6. 테스트에서 안전하게 검증하기

자동 구성은 ApplicationContextRunner 로 가볍게 테스트할 수 있습니다.

class MyAutoConfigTests {
    private final ApplicationContextRunner ctx = new ApplicationContextRunner()
        .withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class))
        .withPropertyValues("mylib.enabled=true");

    @Test
    void createsBeanWhenEnabled() {
        ctx.run(context -> assertThat(context).hasSingleBean(MyService.class));
    }
}

Spring 팀이 제공하는 테스트 도우미로 조건 충족/불충족 케이스를 빠르게 확인합니다.

실전 예제

여기서는 커스텀 라이브러리(Acme Logger) 를 자동 구성으로 제공하는 최소 예시를 만듭니다.

1. 프로퍼티 클래스

@ConfigurationProperties(prefix = "acme.logger")
public class AcmeLoggerProperties {
    private String apiKey;
    private boolean enabled = true;
    private String level = "INFO";
    // getters/setters
}

2. 자동 구성 클래스

@AutoConfiguration
@EnableConfigurationProperties(AcmeLoggerProperties.class)
@ConditionalOnClass(name = "com.acme.logger.AcmeLogger")         // 외부 라이브러리 존재 시만
@ConditionalOnProperty(prefix = "acme.logger", name = "enabled", matchIfMissing = true)
@ConditionalOnMissingBean(type = "com.acme.logger.AcmeLogger")   // 사용자가 정의했으면 백오프
public class AcmeLoggerAutoConfiguration {

    @Bean
    public Object acmeLogger(AcmeLoggerProperties props) throws Exception {
        Class<?> cls = Class.forName("com.acme.logger.AcmeLogger");
        return cls.getConstructor(String.class, String.class)
                  .newInstance(props.getApiKey(), props.getLevel());
    }
}

문자열 기반 조건을 사용하면 컴파일 타임 의존성을 피할 수 있습니다. (자동 구성 작성 가이드는 공식 문서 참고.)

3. 등록 파일 (Boot 3.x)

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

com.example.autoconfig.AcmeLoggerAutoConfiguration

4. Starter 모듈로 묶기

실무에서는 acme-logger-core(구현체) + acme-logger-autoconfigure(위 자동 구성) + acme-logger-starter(둘을 모아 제공) 구조로 배포할 수 있습니다. 사용자는 starter 하나만 추가하면 됩니다.

5. 동작 확인

  • --debug로 조건 평가 리포트를 봅니다.
  • Actuator를 켠 뒤 /actuator/conditions에서 적용/미적용 이유를 확인합니다.

번외: Spring 으로 직접 Spring boot 처럼 환경 구축해보기

Spring(부트 미사용)만으로도 Boot와 유사한 구성을 만들 수 있습니다. 차이를 체감할 수 있도록 JPA 환경을 수동으로 구성해 보았습니다.

@Configuration
@EnableTransactionManagement
@PropertySource("classpath:application.properties")
public class PlainSpringJpaConfig {

    @Autowired Environment env;

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(env.getProperty("db.driver"));
        ds.setUrl(env.getProperty("db.url"));
        ds.setUsername(env.getProperty("db.username"));
        ds.setPassword(env.getProperty("db.password"));
        return ds;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean emf() {
        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(dataSource());
        emf.setPackagesToScan("com.example.domain");
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setShowSql(Boolean.parseBoolean(env.getProperty("jpa.show-sql","false")));
        emf.setJpaVendorAdapter(adapter);
        Properties jpaProps = new Properties();
        jpaProps.put("hibernate.hbm2ddl.auto", env.getProperty("jpa.ddl-auto","none"));
        emf.setJpaProperties(jpaProps);
        return emf;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}
# application.properties (Plain Spring)
db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/mydb
db.username=user
db.password=password
jpa.ddl-auto=update
jpa.show-sql=true