Spring Boot의 역할
Spring Boot가 서버·의존성·자동 구성·외부 설정을 조립하는 방식과 재정의 경계를 이해합니다.
Spring Framework는 객체를 만들고 연결하는 컨테이너와 웹 MVC 같은 핵심 기능을 제공합니다. 그러나 내장 웹 서버, 라이브러리 버전 조합, JSON 변환기, 설정 파일, 실행 JAR까지 개발자가 하나씩 연결하면 첫 프로젝트를 띄우기도 전에 결정할 일이 많습니다.
Spring Boot는 Spring 애플리케이션을 빠르게 시작할 수 있도록 검증된 의존성 조합과 기본 설정을 제공하는 도구입니다. 스타터(starter)는 한 기능에 필요한 라이브러리를 묶고, 자동 구성(auto-configuration)은 클래스 경로, 설정, 사용자 빈을 보고 필요한 기본 빈을 등록합니다. Boot가 Spring을 대체하는 것은 아닙니다. Spring 위에서 반복되는 조립을 줄이고, 애플리케이션이 필요한 부분만 빈이나 속성으로 바꾸게 해 줍니다.
BOOT · INPUTS AND CONDITIONAL DEFAULTS
자동 구성은 입력 조건에 맞는 기본값을 제안한다
Boot는 Spring의 반복 조립을 줄입니다. 스타터가 제공한 클래스패스, 환경·속성, 사용자 빈 정의를 보고 기본 구성을 선택합니다.
입력 · 선택할 근거
- 클래스패스
스타터는 관련 의존성의 묶음입니다. 필요한 기능 후보를 제공하지만 무조건 활성화하지 않습니다.
- 환경 · 프로퍼티
애플리케이션 유형과 속성 값이 구성의 적용 조건을 결정합니다.
- 사용자 빈 정의
해당 조건이 검색하는 역할·타입의 사용자 정의가 있으면 기본 빈이 양보할 수 있습니다.
결과 · 기본값의 소유자
- 조건 불일치 → skip
요구 클래스가 없거나 기능 조건이 맞지 않으면 해당 기본값을 등록하지 않습니다.
- 사용자 정의 → back-off
OnMissingBean 조건에 맞는 사용자 빈이 있으면 기본값은 만들지 않습니다. 사용자 빈은 남습니다.
- 조건 일치 → 기본 빈 등록
빈 생성·검증·서버 시작 등은 이어서 확인합니다. 조건 일치만으로 애플리케이션 준비가 증명되지 않습니다.
조건 보고서의 일치/불일치와 실제 빈 그래프·시작 실패를 함께 봅니다. 기본값 교체에는 속성·커스터마이저·사용자 빈 등 요구에 맞는 작은 확장 지점을 선택합니다.
그림의 위쪽은 공식 문서의 HSQLDB 내장 DataSource 사례로, 다른 필수 조건과 자동 구성 지원이 갖춰졌다고 가정한 판단입니다. HSQLDB 클래스가 없으면 해당 내장 기본값은 등록하지 않고, 클래스가 있으면 사용자 DataSource 빈이 있는지에 따라 사용자 빈에 양보하거나 기본 빈을 등록할 후보가 됩니다. 두 판단은 논리를 설명할 뿐 실제 조건 평가의 내부 순서를 고정하지 않으며, 빈이 등록됐다고 연결이나 스키마가 준비됐다는 뜻도 아닙니다. 아래 칸들은 자동 구성의 입력인 클래스 경로, 환경·속성, 사용자 빈 정의와, 결과인 건너뛰기·양보·기본 빈 등록을 나눠 정리합니다.
반복되는 조립 문제
Boot 없이 웹 애플리케이션을 띄우려면 내장 서버를 만들고 포트를 정한 뒤, 서블릿 컨텍스트에 Spring의 디스패처를 등록하고 루트 컨텍스트의 생명 주기를 묶어야 합니다. 라이브러리 버전이 어긋나면 컴파일은 되는데 시작할 때 NoSuchMethodError가 날 수 있습니다. 운영 환경마다 구성 파일, 로그, 단계적 종료(graceful shutdown)도 필요합니다.
package board.boot;
import java.util.List;
public final class AssemblyInventory {
record Step(String owner, String failureSignal) { }
public static void main(String[] args) {
var steps = List.of(
new Step("web server", "port bind failure"),
new Step("servlet container", "mapping missing"),
new Step("Spring context", "bean creation failure"),
new Step("dependency graph", "linkage error"),
new Step("operations", "health unknown"));
System.out.printf("assemblySteps=%d firstFailure=%s%n",
steps.size(), steps.getFirst().failureSignal());
}
}이 코드는 다섯 가지 설명 값을 목록에 넣고, 그 크기와 첫 항목의 실패 신호를 출력하는 순수 자바 목록입니다. 서버나 서블릿, Spring을 실제로 조립하거나 포트를 열지 않습니다.
assemblySteps=5 firstFailure=port bind failure여기서 firstFailure는 실제로 처음 일어난 장애가 아니라, 목록의 첫 항목에 적은 실패 신호의 이름입니다. 이 출력과, 뒤에 나올 BoardApplication이 실제로 컨텍스트를 시작하는 일을 구별합니다.
Boot는 이 조립 단계를 없애는 것이 아니라, 라이브러리가 검증한 구성에 맡깁니다. 실패가 사라지는 것도 아닙니다. 대신 조건 평가 보고서와 시작 예외로 더 일찍 드러납니다.
스타터와 의존성 관리
스타터는 그 JAR 하나가 기능을 구현한다기보다, 관련된 의존성을 하나의 의도로 모아 둔 것입니다. Web MVC 스타터는 MVC, JSON, 로깅, 내장 서버를 일관된 버전 집합에서 가져옵니다. Bean Validation 구현이 필요하다면 별도의 검증 스타터를 추가합니다.
애플리케이션은 Boot의 BOM이 관리하는 의존성에 버전을 다시 적지 않습니다. 관리 범위 밖의 라이브러리는 버전을 직접 고르고, 보안 취약점과 자바 호환성도 직접 챙깁니다. 전이 의존성의 버전을 재정의한다면, 왜 Boot의 관리 범위를 벗어났는지 기록하고 의존성 분석 결과와 기본 구동 확인을 함께 남깁니다.
스타터를 일단 다 넣어 두는 방식은 클래스 경로를 키우고, 원하지 않는 자동 구성을 활성화합니다. 실제로 쓰는 기능을 기준으로 고르고, 쓰지 않는 데이터베이스 드라이버나 Actuator 메트릭 내보내기 구현체는 제거합니다.
조건부 자동 구성
Boot는 자동 구성 후보 목록을 읽고, 현재 환경의 조건에 맞는 빈을 등록합니다. MVC 클래스가 있는지, 리액티브 클래스는 없는지, 사용자가 이미 빈을 정의했는지, 어떤 속성이 켜졌는지 같은 사실이 조건의 입력입니다. 사용자 빈이 있으면 자동 구성의 기본 빈이 물러나는 규칙이 가장 중요한 재정의 방법입니다.
@SpringBootApplication은 구성 클래스 선언, 컴포넌트 스캔, 자동 구성 활성화를 하나로 묶습니다. 메인 클래스의 패키지가 스캔의 루트가 되므로, 메인 클래스를 기본 패키지나 너무 넓은 패키지에 두면 불필요한 타입까지 탐색합니다.
자동 구성을 이해할 때는 빈 이름만 찾지 말고 조건 평가 보고서를 읽습니다. 조건이 맞은 항목, 맞지 않은 항목, 조건 없이 적용된 클래스를 구분하면, 빈이 왜 만들어졌는지와 왜 만들어지지 않았는지를 같은 방식으로 설명할 수 있습니다.
공식 자동 구성 설명의 내장 DB 사례로 읽어 보면 이렇습니다. HSQLDB 클래스가 있고 자동 구성 지원과 다른 필수 조건도 맞으면, 기본 DataSource를 등록할 후보가 생깁니다. 같은 역할의 사용자 DataSource 빈을 직접 등록하면 그 기본값은 물러나고 사용자 빈이 남습니다. 필요한 클래스가 없는 경우와 사용자 빈 때문에 양보한 경우는 기본값이 없다는 결과는 같아도 이유가 다릅니다.
모든 자동 구성이 아무 사용자 빈 하나 때문에 꺼지는 것은 아닙니다. 그 조건이 어떤 역할과 타입을 찾는지 보고서에서 확인해야 합니다. 앞의 MVC와 리액티브 문장도 조건 입력의 예일 뿐이며, 두 기술이 함께 있는 클래스 경로에서 최종 애플리케이션 유형이 무엇이 될지를 이 한 문장으로 정하지 않습니다.
SpringApplication의 준비 과정
SpringApplication은 실행 인자, 시스템 속성, 환경 변수, 구성 데이터를 환경(environment)으로 모으고, 애플리케이션의 유형을 판단한 뒤 컨텍스트를 만들고 새로 고침(refresh)합니다. 리스너, 초기화기, 실행기는 서로 다른 생명 주기 단계에서 동작합니다.
package board;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BoardApplication {
public static void main(String[] args) {
SpringApplication.run(BoardApplication.class, args);
}
}이 클래스는 1-1에서 누적 프로젝트에 만든 시작 클래스와 같으므로 새로 만들지 않습니다. @SpringBootApplication이 붙은 메인 클래스가 둘이 되면 Boot Gradle 플러그인이 실행 JAR의 시작 클래스를 하나로 정하지 못하고 Unable to find a single main class 오류로 멈춥니다.
메인 메서드는 짧지만, 빌드의 클래스 경로와 애플리케이션 속성이 실제 실행의 입력입니다. 테스트에서 다른 메인 소스를 쓰면 스캔과 자동 구성의 범위가 달라질 수 있으므로, 실제 애플리케이션 클래스로 최소 구성의 컨텍스트를 시작해 검증합니다.
재정의할 수 있는 기본값
포트, JSON 동작, 오류 정보 노출, 연결 풀 크기는 기본값을 그대로 쓸 수 있지만, 운영 환경의 요구에 맞는지는 확인해야 합니다. 재정의할 때는 사용자 빈, 속성, 커스터마이저, 제외 가운데 가장 작은 방법을 고릅니다. 자동 구성 클래스 전체를 복사하면 업그레이드의 이점을 잃습니다.
자동 구성의 제외는 특정 기본값이 애플리케이션의 아키텍처와 맞지 않을 때 쓰고, 그 이유와 대체 빈을 기록합니다. 시작만 통과시키려고 제외를 늘리면 필수 기능이 빠질 수 있습니다.
Boot 버전을 올릴 때는 릴리스 마이그레이션 안내서, 의존성 차이, 조건 평가 보고서의 차이, 통합 테스트를 확인합니다. 컴파일이 성공했다고 해서 생성된 구성의 의미까지 같다고 보지 않습니다.
시작 단계와 실행 단계의 진단
빈 정의 충돌, 누락된 클래스, 잘못된 구성 속성은 시작 단계에서 실패시키는 편이 안전합니다. 반면 데이터베이스의 일시적 장애처럼 실행 중에 변하는 상태는 상태 확인과 재시도로 표현할 수 있습니다. 다만 필수 스키마가 없는데 준비 완료로 표시하지는 않습니다.
시작 시간, 빈 개수, 조건 평가 결과, 웹 서버 포트를 기준선으로 남겨 두면, 스타터를 추가했을 때 애플리케이션에 어떤 영향이 있었는지 찾기 쉽습니다. 디버그 보고서는 운영 환경의 모든 부팅에서 출력하지 말고, 문제를 재현하는 환경에서만 켭니다.
수동 조립과 Boot 기본값을 비교할 때 설정 파일의 수만 세지는 않습니다. 첫 HTTP 응답까지 걸린 시간, 잘못된 속성이 시작 단계에서 막히는지, 단계적 종료 중에 유실된 요청이 있는지, 의존성 업그레이드 뒤 조건이 어떻게 바뀌었는지를 같은 인수 시나리오로 측정합니다. 관례가 이런 결과를 안정적으로 낼 때만 그대로 두고, 요구가 달라지면 가장 작은 사용자 정의 방법으로 고칩니다.
정리
- Spring Boot는 Spring을 대체하지 않고, 검증된 의존성 조합과 조건부 기본 설정으로 반복되는 조립을 줄입니다.
- 스타터는 한 기능에 필요한 의존성 묶음이며, 버전은 Boot의 BOM이 관리합니다.
- 자동 구성은 클래스 경로, 속성, 사용자 빈을 조건으로 기본 빈을 등록하거나 사용자 빈에 양보하며, 그 이유는 조건 평가 보고서로 확인합니다.
- 기본값을 바꿀 때는 속성, 커스터마이저, 사용자 빈, 제외 가운데 가장 작은 방법을 고르고 이유를 남깁니다.
연습 문제
1. 수동 조립 단계와 Boot의 대응
Boot 없이 MVC 서버를 조립할 때 필요한 단계와, Boot가 제공하는 대응 기능을 표로 만드세요. 각 기본값을 그대로 쓸 조건, 사용자 빈으로 재정의할 조건, 완전히 제외할 조건을 회원 게시판의 요구에 맞게 정하세요.
해설 보기
기본값을 고를 때는 기능이 있는지만이 아니라 운영에서의 결과까지 포함해 판단합니다. 예를 들어 내장 서버는 그대로 쓰더라도 스레드 수, 타임아웃, 단계적 종료를 실제 작업 부하로 검증합니다. 결정 하나를 다음처럼 기록하면, 이유를 적지 않은 결정이 남는 일을 생성 시점에 막을 수 있습니다.
package board.boot;
public record BootDecision(
String capability,
Choice choice,
String evidence
) {
public enum Choice { DEFAULT, OVERRIDE, EXCLUDE }
public BootDecision {
if (capability.isBlank() || evidence.isBlank()) {
throw new IllegalArgumentException("decision evidence required");
}
}
}결정마다 다시 검토할 계기와 그 결정을 지키는 테스트를 함께 적어, 버전을 올릴 때 다시 평가합니다.
2. 기본 DataSource가 없는 두 가지 이유
다른 필수 조건은 모두 맞다고 가정합니다. A는 HSQLDB 클래스가 있고 사용자 DataSource가 없습니다. B는 클래스가 있고 사용자 DataSource가 있습니다. C는 클래스가 없습니다. 각 경우에 내장 기본 DataSource가 등록되는지와, 등록되지 않는다면 그 이유를 구분하세요. B에서 사용자 빈이 만들어졌다는 사실만으로 실제 DB 연결 성공까지 확인되나요?
해설 보기
A는 기본 빈을 등록할 후보가 됩니다. B는 사용자 정의에 양보하므로 기본 빈을 만들지 않고 사용자 빈을 씁니다. C는 클래스 조건이 맞지 않아 내장 기본값을 등록하지 않습니다.
B에서 빈이 있다는 것은 그 역할이 연결됐다는 뜻일 뿐이며, 연결과 스키마, 준비 상태는 따로 검사해야 합니다. 조건 일치, 빈 생성, 실제 동작은 서로 다른 단계입니다.
3. 목록의 첫 항목과 실제 첫 장애
AssemblyInventory의 목록에서 operations 항목을 맨 앞으로 옮기면 출력은 어떻게 바뀌나요? 이 결과로 운영 환경에서 상태를 알 수 없는 문제가 가장 먼저 일어난다고 말할 수 있는지도 답하세요.
해설 보기
항목 수는 그대로이고 첫 항목만 바뀌므로 출력은 assemblySteps=5 firstFailure=health unknown이 됩니다.
firstFailure는 목록의 순서를 바꾸면 따라 바뀌는 첫 항목의 이름일 뿐, 실제로 관찰한 장애의 순서가 아닙니다. 어떤 실패가 먼저 일어나는지는 실제로 조립해 시작해 보거나 장애를 주입해 확인해야 하며, 설명용 목록의 순서로 정할 수 없습니다.
다음 글 서블릿 컨테이너 수동 조립에서는 Boot가 대신해 주는 일을 직접 확인하기 위해, 내장 Tomcat과 Spring의 디스패처 서블릿을 손으로 연결해 봅니다.