Spring Boot Starter란?
Spring Boot Starter는 특정 기능에 필요한 의존성과 자동 설정을 하나의 모듈로 묶어 제공하는 패턴입니다. spring-boot-starter-web처럼 의존성 하나만 추가하면 관련 설정이 자동으로 적용됩니다. 이 글에서는 팀 내부용 Custom Starter를 직접 만드는 방법 — 자동 설정, @ConfigurationProperties, META-INF/spring 등록, 조건부 설정까지 심화 내용을 다룹니다.
Starter 프로젝트 구조
Custom Starter는 보통 두 모듈로 구성합니다:
my-starter/
├── my-starter-autoconfigure/ # 자동 설정 로직
│ ├── src/main/java/
│ │ └── com/example/starter/
│ │ ├── MyServiceAutoConfiguration.java
│ │ ├── MyServiceProperties.java
│ │ └── MyService.java
│ └── src/main/resources/
│ └── META-INF/
│ └── spring/
│ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
└── my-starter/ # 의존성만 모은 빈 모듈
└── build.gradle # autoconfigure + 필요 라이브러리 의존
소규모 Starter는 하나의 모듈로도 충분합니다. 이 글에서는 단일 모듈 방식으로 설명합니다.
@ConfigurationProperties: 설정 바인딩
사용자가 application.yml에서 Starter 동작을 제어할 수 있도록 Properties 클래스를 정의합니다:
@ConfigurationProperties(prefix = "my.notification")
public class NotificationProperties {
/**
* 알림 기능 활성화 여부
*/
private boolean enabled = true;
/**
* Slack Webhook URL
*/
private String slackWebhookUrl;
/**
* 기본 채널 이름
*/
private String defaultChannel = "#general";
/**
* 재시도 설정
*/
private Retry retry = new Retry();
// Getters, Setters...
public static class Retry {
private int maxAttempts = 3;
private long delayMs = 1000;
private double multiplier = 2.0;
// Getters, Setters...
}
}
# 사용자의 application.yml
my:
notification:
enabled: true
slack-webhook-url: https://hooks.slack.com/services/xxx
default-channel: "#alerts"
retry:
max-attempts: 5
delay-ms: 2000
서비스 클래스 구현
public class NotificationService {
private final NotificationProperties properties;
private final RestClient restClient;
public NotificationService(NotificationProperties properties,
RestClient.Builder restClientBuilder) {
this.properties = properties;
this.restClient = restClientBuilder.build();
}
public void sendSlack(String message) {
sendSlack(properties.getDefaultChannel(), message);
}
public void sendSlack(String channel, String message) {
if (!properties.isEnabled()) {
return;
}
Map<String, String> payload = Map.of(
"channel", channel,
"text", message
);
executeWithRetry(() ->
restClient.post()
.uri(properties.getSlackWebhookUrl())
.contentType(MediaType.APPLICATION_JSON)
.body(payload)
.retrieve()
.toBodilessEntity()
);
}
private void executeWithRetry(Runnable action) {
var retry = properties.getRetry();
long delay = retry.getDelayMs();
for (int i = 0; i < retry.getMaxAttempts(); i++) {
try {
action.run();
return;
} catch (Exception e) {
if (i == retry.getMaxAttempts() - 1) throw e;
try { Thread.sleep(delay); } catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
delay = (long) (delay * retry.getMultiplier());
}
}
}
}
AutoConfiguration: 자동 설정
핵심인 자동 설정 클래스입니다. @Conditional 어노테이션으로 조건부로 Bean을 등록합니다:
@AutoConfiguration
@EnableConfigurationProperties(NotificationProperties.class)
@ConditionalOnProperty(
prefix = "my.notification",
name = "enabled",
havingValue = "true",
matchIfMissing = true // 설정 없으면 기본 활성화
)
public class NotificationAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 사용자가 직접 정의하면 덮어쓰지 않음
public NotificationService notificationService(
NotificationProperties properties,
RestClient.Builder restClientBuilder) {
return new NotificationService(properties, restClientBuilder);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(MeterRegistry.class) // Micrometer 있을 때만
public NotificationMetrics notificationMetrics(
MeterRegistry registry) {
return new NotificationMetrics(registry);
}
}
주요 @Conditional 어노테이션들입니다:
| 어노테이션 | 조건 |
|---|---|
@ConditionalOnMissingBean |
해당 타입의 Bean이 없을 때만 등록 |
@ConditionalOnBean |
특정 Bean이 존재할 때만 등록 |
@ConditionalOnClass |
클래스패스에 특정 클래스가 있을 때 |
@ConditionalOnProperty |
설정 프로퍼티 값에 따라 |
@ConditionalOnWebApplication |
웹 애플리케이션일 때만 |
이 조건부 설정에 대한 더 깊은 내용은 Spring @Conditional 자동 설정을 참고하세요.
Spring Boot 3 등록: AutoConfiguration.imports
Spring Boot 3에서는 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일에 자동 설정 클래스를 등록합니다:
# src/main/resources/META-INF/spring/
# org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.starter.NotificationAutoConfiguration
주의: Spring Boot 2.x의 spring.factories 방식은 3.x에서 deprecated입니다.
IDE 자동완성: additional-spring-configuration-metadata.json
사용자가 application.yml에서 자동완성과 설명을 볼 수 있도록 메타데이터를 추가합니다:
// src/main/resources/META-INF/additional-spring-configuration-metadata.json
{
"properties": [
{
"name": "my.notification.enabled",
"type": "java.lang.Boolean",
"description": "알림 기능 활성화 여부. false로 설정하면 모든 알림이 비활성화됩니다.",
"defaultValue": true
},
{
"name": "my.notification.slack-webhook-url",
"type": "java.lang.String",
"description": "Slack Incoming Webhook URL"
},
{
"name": "my.notification.default-channel",
"type": "java.lang.String",
"description": "기본 Slack 채널",
"defaultValue": "#general"
}
],
"hints": [
{
"name": "my.notification.default-channel",
"values": [
{"value": "#general", "description": "일반 채널"},
{"value": "#alerts", "description": "알림 채널"},
{"value": "#dev", "description": "개발 채널"}
]
}
]
}
build.gradle 설정
// build.gradle
plugins {
id 'java-library'
id 'maven-publish'
id 'org.springframework.boot' version '3.3.0' apply false
id 'io.spring.dependency-management' version '1.1.5'
}
dependencyManagement {
imports {
mavenBom org.springframework.boot.gradle.plugin
.SpringBootPlugin.BOM_COORDINATES
}
}
dependencies {
api 'org.springframework.boot:spring-boot-starter'
api 'org.springframework.boot:spring-boot-starter-web'
// 선택적 의존성 (있으면 활성화)
compileOnly 'io.micrometer:micrometer-core'
annotationProcessor
'org.springframework.boot:spring-boot-configuration-processor'
// ↑ ConfigurationProperties 메타데이터 자동 생성
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
// Maven 로컬/사내 저장소에 배포
publishing {
publications {
maven(MavenPublication) {
groupId = 'com.example'
artifactId = 'my-notification-starter'
version = '1.0.0'
from components.java
}
}
}
spring-boot-configuration-processor는 @ConfigurationProperties에서 메타데이터 JSON을 자동 생성합니다.
테스트 작성
@SpringBootTest(classes = NotificationAutoConfiguration.class)
@EnableConfigurationProperties(NotificationProperties.class)
class NotificationAutoConfigurationTest {
// 기본 설정으로 Bean이 등록되는지
@Test
void autoConfiguresNotificationService() {
new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(NotificationAutoConfiguration.class))
.withPropertyValues(
"my.notification.slack-webhook-url=https://hooks.slack.com/test")
.run(context -> {
assertThat(context).hasSingleBean(NotificationService.class);
});
}
// enabled=false이면 Bean이 없어야 함
@Test
void disabledWhenPropertyIsFalse() {
new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(NotificationAutoConfiguration.class))
.withPropertyValues("my.notification.enabled=false")
.run(context -> {
assertThat(context)
.doesNotHaveBean(NotificationService.class);
});
}
// 사용자가 직접 Bean을 정의하면 Starter 것은 등록 안 됨
@Test
void backsOffWhenUserDefinesBean() {
new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(NotificationAutoConfiguration.class))
.withUserConfiguration(CustomConfig.class)
.run(context -> {
assertThat(context).hasSingleBean(NotificationService.class);
assertThat(context.getBean(NotificationService.class))
.isInstanceOf(CustomNotificationService.class);
});
}
@Configuration
static class CustomConfig {
@Bean
NotificationService notificationService() {
return new CustomNotificationService();
}
}
}
ApplicationContextRunner는 Spring Boot의 자동 설정 테스트를 위한 핵심 도구입니다. 경량 컨텍스트를 빠르게 생성해 검증합니다.
사용자 측 사용법
// build.gradle
dependencies {
implementation 'com.example:my-notification-starter:1.0.0'
}
// application.yml
my:
notification:
slack-webhook-url: ${SLACK_WEBHOOK_URL}
default-channel: "#backend-alerts"
// 서비스에서 주입받아 사용
@Service
@RequiredArgsConstructor
public class OrderService {
private final NotificationService notificationService;
public void createOrder(OrderRequest request) {
// 주문 생성 로직...
notificationService.sendSlack(
"새 주문: " + request.getOrderId()
);
}
}
의존성 추가와 yml 설정만으로 기능이 활성화됩니다. 이 선언적 설정 방식은 Spring ApplicationEvent 이벤트 설계와 함께 사용하면 이벤트 기반 알림 시스템을 깔끔하게 구축할 수 있습니다.
운영 베스트 프랙티스
- 네이밍 규칙: 공식 Starter는
spring-boot-starter-*, 커스텀은*-spring-boot-starter - @ConditionalOnMissingBean 필수: 사용자가 커스터마이징할 수 있도록 항상 추가
- 선택적 의존성:
compileOnly+@ConditionalOnClass로 선택적 기능 제공 - 문서화: Properties에 Javadoc 주석 작성 → IDE 자동완성에 표시됨
- 버전 관리: Semantic Versioning 준수, Breaking Change 시 메이저 버전 업
- Failure Analyzer: 설정 오류 시 친절한 메시지를 제공하는 FailureAnalyzer 구현 고려
마무리
Spring Boot Custom Starter는 팀의 공통 기능을 모듈화하는 가장 Spring스러운 방법입니다. @ConfigurationProperties로 선언적 설정을, @Conditional로 조건부 활성화를, @ConditionalOnMissingBean으로 확장 가능성을 제공하면, 사용자는 의존성 추가만으로 기능을 사용하고 필요 시 커스터마이징할 수 있습니다.