Spring Boot 3의 핵심 변화
Spring Boot 3은 Java 17 이상을 필수로 요구하며, Jakarta EE 10 네임스페이스(javax.* → jakarta.*)로 전환되었습니다. GraalVM 네이티브 이미지를 공식 지원하고, 관찰 가능성(Observability)이 Micrometer 기반으로 통합되었습니다.
Spring Initializr를 통해 프로젝트를 생성하는 것이 가장 빠른 시작 방법입니다. 핵심 의존성은 spring-boot-starter-web이며, 이 하나만으로 내장 Tomcat, Jackson JSON 처리, Spring MVC가 모두 포함됩니다.
프로젝트 생성과 기본 구조
build.gradle 설정
// build.gradle — Spring Boot 3 의존성 설정
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.4'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.example'
version = '1.0.0'
java {
sourceCompatibility = '17' // Java 17 이상 필수
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' // REST API 핵심
implementation 'org.springframework.boot:spring-boot-starter-validation' // 입력 검증
testImplementation 'org.springframework.boot:spring-boot-starter-test' // 테스트
}
메인 애플리케이션 클래스
// Application.java — Spring Boot 진입점
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // 자동 설정 + 컴포넌트 스캔 + 설정 클래스 역할
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
// 실행 결과:
// Started Application in 1.234 seconds (process running for 1.567)
// Tomcat started on port 8080
}
}
@SpringBootApplication은 세 가지 어노테이션의 조합입니다. @EnableAutoConfiguration은 클래스패스의 라이브러리를 감지하여 자동 설정을 적용합니다. @ComponentScan은 현재 패키지와 하위 패키지의 빈을 자동 등록합니다. @Configuration은 이 클래스 자체를 설정 클래스로 표시합니다.
REST API 구현
도메인 모델과 컨트롤러
// TaskController.java — CRUD REST API 구현
package com.example.demo.controller;
import com.example.demo.dto.TaskRequest;
import com.example.demo.dto.TaskResponse;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
// DTO — record로 간결하게 정의
record TaskRequest(
@jakarta.validation.constraints.NotBlank(message = "제목은 필수입니다")
String title,
String description
) {}
record TaskResponse(
Long id,
String title,
String description,
boolean completed,
LocalDateTime createdAt
) {}
@RestController // JSON 응답 자동 직렬화
@RequestMapping("/api/tasks") // 공통 경로 접두사
public class TaskController {
// 인메모리 저장소 (실제로는 JPA Repository 사용)
private final Map<Long, TaskResponse> store = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
// GET /api/tasks — 전체 조회
@GetMapping
public List<TaskResponse> getAll() {
return new ArrayList<>(store.values());
// 응답 예시: [{"id":1,"title":"공부","completed":false,...}]
}
// GET /api/tasks/{id} — 단건 조회
@GetMapping("/{id}")
public ResponseEntity<TaskResponse> getById(@PathVariable Long id) {
TaskResponse task = store.get(id);
if (task == null) {
return ResponseEntity.notFound().build(); // 404 반환
}
return ResponseEntity.ok(task); // 200 + JSON 본문
}
// POST /api/tasks — 생성
@PostMapping
public ResponseEntity<TaskResponse> create(@Valid @RequestBody TaskRequest req) {
Long id = idGenerator.getAndIncrement();
TaskResponse task = new TaskResponse(
id, req.title(), req.description(), false, LocalDateTime.now()
);
store.put(id, task);
return ResponseEntity.status(HttpStatus.CREATED).body(task);
// 201 Created + 생성된 리소스 반환
}
// DELETE /api/tasks/{id} — 삭제
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
if (store.remove(id) == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.noContent().build(); // 204 No Content
}
}
전역 예외 처리
// GlobalExceptionHandler.java — 통합 에러 응답
package com.example.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.time.LocalDateTime;
import java.util.List;
record ErrorResponse(int status, String message, LocalDateTime timestamp) {}
@RestControllerAdvice // 모든 컨트롤러에 적용되는 예외 처리기
public class GlobalExceptionHandler {
// 검증 실패 시 처리
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(
MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.toList();
ErrorResponse response = new ErrorResponse(
400,
String.join(", ", errors),
LocalDateTime.now()
);
return ResponseEntity.badRequest().body(response);
// 결과: {"status":400,"message":"title: 제목은 필수입니다","timestamp":"..."}
}
}
자동 설정(Auto-Configuration) 이해
Spring Boot의 자동 설정은 “관례 우선(Convention over Configuration)” 원칙을 따릅니다. spring-boot-starter-web을 추가하면 다음이 자동으로 설정됩니다.
| 감지 조건 | 자동 설정 내용 |
|---|---|
| 클래스패스에 Tomcat | 내장 웹 서버 구동 |
| 클래스패스에 Jackson | JSON 직렬화/역직렬화 |
@RestController 존재 | Spring MVC 디스패처 서블릿 |
application.properties | 포트, 로깅 등 커스텀 설정 |
application.properties에서 주요 설정을 오버라이드할 수 있습니다.
// application.properties — 주요 설정 항목
// server.port=9090 // 포트 변경 (기본 8080)
// spring.jackson.date-format=yyyy-MM-dd // JSON 날짜 형식
// logging.level.root=INFO // 로그 레벨
// spring.profiles.active=dev // 활성 프로파일
// 프로파일별 설정 파일
// application-dev.properties → 개발 환경
// application-prod.properties → 운영 환경
프로파일과 외부 설정
// AppConfig.java — @ConfigurationProperties 패턴
package com.example.demo.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "app") // app.* 설정을 바인딩
public record AppConfig(
String name, // app.name
int maxRetries, // app.max-retries (케밥 → 카멜 자동 변환)
String apiUrl // app.api-url
) {}
// application.properties에서:
// app.name=TaskManager
// app.max-retries=3
// app.api-url=https://api.example.com
// 사용 예시:
// @Service
// public class MyService {
// private final AppConfig config;
// public MyService(AppConfig config) {
// this.config = config;
// System.out.println(config.name()); // "TaskManager"
// }
// }
정리
Spring Boot 3을 시작할 때 기억해야 할 핵심 사항입니다.
- Java 17 이상이 필수이며,
javax.*→jakarta.*마이그레이션이 필요합니다 @SpringBootApplication하나로 자동 설정, 컴포넌트 스캔, 설정 클래스가 통합됩니다- REST API는
@RestController+@RequestMapping조합으로 구현하고,record를 DTO로 활용하면 보일러플레이트를 줄일 수 있습니다 @Valid와@RestControllerAdvice로 입력 검증과 예외 처리를 분리합니다@ConfigurationProperties로 타입 안전한 설정 바인딩을 사용합니다- 프로파일(
spring.profiles.active)로 환경별 설정을 관리합니다
Spring Boot의 강점은 “시작은 빠르게, 확장은 유연하게”입니다. 기본 설정으로 빠르게 시작하고, 필요할 때 자동 설정을 오버라이드하여 세밀하게 제어할 수 있습니다.