실시간 통신은 채팅, 알림, 게임. 금융 거래 등 다양한 분야에서 핵심 기술로 사용된다. 예를 들어, 소셜 미디어에서 친구가 게시물을 올릴 때 실시간 알림이 전송되는 경우, WebSocket을 통해 효율적으로 구현할 수 있다. 추후에 어떤 서비스를 구현해 볼지 모르기 때문에 한번 관련된 내용을 정리해두자
기본 개념을 먼저 이해해보자
1. HTTP 프로토콜
HTTP(HyperText Transfer Protocol)는 웹의 기본 통신 프로토콜이다.
요청-응답 구조 : 클라이언트가 요청하면 서버가 응답
단발성 연결 : 요청 후 연결 종료
비상태성 : 각 요청이 독립적
위처럼 HTTP의 한계는 실시간 상호작용에 적합하지 않다는 점이다. 실시간 데이터 전송이나 서버의 지속적인 이벤트 알림은 HTTP만으로 구현하기 어렵다.
2. WebSocket이란?
WebSocket은 HTTP와 달리 양방향, 지속적인 연결을 제공하는 프로토콜이다.
지속 연결 : 클라이언트와 서버가 한 번 연결되면 계속해서 데이터를 주고받을 수 있다.
양방향 통신 : 서버가 클라이언트에게 자유롭게 메시지를 전송할 수 있다.
실시간성 : 낮은 지연시간으로 실시간 데이터를 처리할 수 있다.
HTTP와 WebSocket의 차이점을 표로 만들어보자
구분
HTTP
WebSocket
연결 방식
요청-응답 후 연결 종료
연결 후 지속적으로 열린 상태 유지
통신 방향
단방향(클라이언트 요청에 의존)
양방향(서버와 클라이언트 모두 자유롭게 전송)
프로토콜 오버헤드
매 요청마다 헤더 정보 전송
초기 핸드쉐이크 후 최소한의 오버헤드
실시간성
제한적(폴링 방식 필요)
매우 우수 (즉시 데이터 전달)
실시간 애플리케이션에서 왜 WebSocket을 선호하는지 알 것 같다. 그러면 WebSocket의 동작 원리에 대해서 알아보자
3. WebSocket의 동작 원리
3.1 핸드쉐이크 과정
WebSocket 통신은 HTTP 핸드쉐이크로 시작한다.
클라이언트 요청 : 클라이언트는 HTTP 업그레이드 헤더를 포함하여 서버에 연결 요청을 보낸다.
서버 응답 : 서버는 요청을 수락하고, 프로토콜을 WebSocket으로 전환하는 응답을 보낸다.
연결 수립 : 이후 연결은 TCP기반의 지속 연결로 전황되어 데이터를 주고받는다.
이 과정을 통해 기존 HTTP 환경에서도 WebSocket을 사용할 수 있는 유연성을 제공한다.
3.2 연결 유지 및 메시지 교환
연결이 성립되면 클라이언트와 서버는 프레임 단위로 메시지를 주고받는다.
텍스트 프레임 : 일반 텍스트 메시지 전송
바이너리 프레임 : 이미지, 파일 등 이진 데이터 전송
컨트롤 프레임 : 연결 종료, 핑/퐁 등 관리 메시지 전송
3.3 연결 종료 메커니즘
연결 종료 시에는 양측에서 종료 요청을 보내고, 지정된 절차에 따라 연결을 정상적으로 마감한다. 이 과정을 예기치 않은 연결 종료를 방지하고, 자원 누수를 최소화한다.
Spring Boot를 활용해서 간단한 서비스를 만들어보자
Spring Boot에서는 기본적으로 WebSocket을 지원하고, 모듈도 제공해준다.
spring-boot-starter-websocket : WebSocket 관련 의존성 자동 구성
STOMP(Simple Text Oriented Messaging Protocol) : 메시지 브로커와의 통신 지원
Spring Boot에서 WebSocket을 사용하려면 Gradle 또는 Maven을 통해 의존성을 추가해준다.
* 관련해서 더 알아보아야 하는 것 : Docker를 이용해 여러 개의 Spring Boot 컨테이너를 실행하고, Kubernetes를 사용하여 Auto Scaling 기능을 적용하는 방법을 찾아보자.
3-2. 부하 분산(Load Balancing)
부하 분산은 사용자 요청을 여러 서버에 골고루 분산시켜 한 서버에 부담이 집중되지 않도록 하는 기술이다. 음식점에서 한 웨이터가 모든 손님을 상대하기 어려우니, 여러 웨이터가 각 테이블을 나누어 케어하는 것과 같다.
Spring Boot 애플리케이션은 외부 로드 밸런서(Nginx, HAProxy, AWS ELB 등)와 함께 사용하여 부하 분산을 쉽게 구현할 수 있다.
직접 로드 밸런싱 코드를 작성하는게 아니라 로드 밸런서 설정 파일을 통해 서버 간 트래픽 분산을 관리한다.
Nginx 설정 일부 예시 :
upstream spring_backend {
server 192.168.1.101:8080;
server 192.168.1.102:8080;
server 192.168.1.103:8080;
}
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://spring_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
3-3. 캐싱(Caching)
캐싱은 자주 사용되는 데이터를 미리 저장해 두어, 데이터베이스나 다른 서버에 매번 접근하지 않고 빠르게 응답하는 기술이다. 냉장고에 좋아하는 간식을 미리 저장해두면, 매번 마트에 가지 않고도 간식을 즐길 수 있는 걸 생각하면 비슷하다.
Spring Boot는 Redis와 같은 캐시 솔루션과 쉽게 통합할 수 있다.
Redis 캐시 설정 및 사용 예시 : 1. build.gradle.kts에 Redis 의존성 추가
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-redis")
// ... 기타 의존성
}
2. applicaiton.properties에 Redis 설정 추가
# Redis 서버 설정 (로컬에서 Redis가 실행 중임을 가정합니다)
spring.redis.host=localhost
spring.redis.port=6379
# 기본 서버 포트 (8080번 포트로 실행)
server.port=8080
3. Kotlin 코드 예제 - 캐시 서비스 구현
CacheService.kt
package com.example.demo.service
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Service
import java.util.concurrent.TimeUnit
/**
* CacheService 클래스는 Redis를 이용하여 캐시 기능을 제공하는 서비스이다.
* 이 클래스에서는 데이터를 캐시에 저장(setCache)하고, 조회(getCache)하는 기능을 구현한다.
*/
@Service
class CacheService(@Autowired val redisTemplate: RedisTemplate<String, String>) {
/**
* setCache 함수는 지정된 key와 value를 캐시에 저장합니다.
* timeout은 캐시에 데이터가 유지될 시간(초)입니다.
*/
fun setCache(key: String, value: String, timeout: Long = 60) {
// opsForValue()는 단순한 key-value 캐싱에 사용됩니다.
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS)
}
/**
* getCache 함수는 지정된 key에 해당하는 캐시된 값을 반환합니다.
* 만약 캐시에 값이 없으면 null을 반환합니다.
*/
fun getCache(key: String): String? {
return redisTemplate.opsForValue().get(key)
}
}
AsyncService.kt
package com.example.demo.service
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service
/**
* AsyncService 클래스는 비동기 작업을 처리하기 위한 서비스이다.
* @Async 어노테이션을 사용하여, 이 클래스의 메서드가 호출될 때 즉시 반환되고,
* 별도의 스레드에서 작업을 수행하게 된다.
*/
@Service
class AsyncService {
/**
* doAsyncWork 함수는 3초 동안 대기한 후 "비동기 작업 완료!" 메시지를 콘솔에 출력한다.
* 이 함수는 @Async 어노테이션 덕분에 호출 즉시 비동기적으로 실행된다.
*/
@Async
fun doAsyncWork() {
// 3초간 대기하여, 긴 작업을 비동기적으로 처리하는 예제를 시뮬레이션한다.
Thread.sleep(3000)
println("비동기 작업 완료!")
}
}
DemoController.kt
package com.example.demo.controller
import com.example.demo.service.AsyncService
import com.example.demo.service.CacheService
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
/**
* DemoController는 REST API를 제공하는 컨트롤러이다.
* 이 컨트롤러는 캐시 기능과 비동기 작업 기능을 테스트하기 위한 API를 제공한다.
*/
@RestController
class DemoController(
val cacheService: CacheService, // CacheService를 주입받아 캐시 기능을 사용한다.
val asyncService: AsyncService // AsyncService를 주입받아 비동기 작업을 수행한다.
) {
/**
* /ping API는 캐시에서 "greeting" 키의 값을 조회하고,
* 값이 없다면 "pong"을 캐시에 저장한다.
* 동시에 비동기 작업을 실행하고, "pong"을 응답한다.
*/
@GetMapping("/ping")
fun ping(): String {
val key = "greeting"
// 캐시에서 key "greeting"의 값을 가져온다.
var value = cacheService.getCache(key)
// 만약 캐시에 값이 없으면,
if (value == null) {
value = "pong"
// 캐시에 60초 동안 "pong" 값을 저장한다.
cacheService.setCache(key, value, 60)
}
// 비동기 작업을 실행한다.
// 이 작업은 백그라운드에서 3초 후 완료된다.
asyncService.doAsyncWork()
// "pong"을 응답으로 반환한다.
return value
}
}
3-4. 비동기 처리와 큐
사용자의 요청을 즉시 처리하지 않고, 큐에 저장한 후 차례대로 처리하는 방식이다. (인터파크에서 앞에 몇명이 남았다고 알려주는게 큐를 사용해서 그런게 아닐까?) 놀이공원에서 사람들이 대기열에 서 있다가 순서대로 놀이기구를 타는 것처럼, 요청을들 순차적으로 처리한다.
@Async 어노테이션을 사용하면 쉽게 비동기 작업을 구현할 수 있다.
간단한 예제를 보면
// AsyncService.kt
package com.example.demo.service
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service
/**
* AsyncService는 긴 작업을 비동기적으로 처리하는 서비스이다.
* @Async 어노테이션을 사용하여, 이 메서드가 호출되면 별도의 스레드에서 실행된다.
*/
@Service
class AsyncService {
/**
* doAsyncWork 함수는 3초간 대기 후 콘솔에 "비동기 작업 완료!" 메시지를 출력한다.
*/
@Async
fun doAsyncWork() {
// 3000 밀리초 (3초) 동안 대기한다.
Thread.sleep(3000)
println("비동기 작업 완료!")
}
}
비동기에 대해서 감이 안올수 있다. Controller에서 비동기를 호출 후 처리하는 간단한 예제를 살펴보자
// DemoController.kt
package com.example.demo.controller
import com.example.demo.service.AsyncService
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
/**
* DemoController는 간단한 REST API를 제공하여, 비동기 작업을 테스트할 수 있게 한다.
*/
@RestController
class DemoController(val asyncService: AsyncService) {
/**
* /asyncTest 경로를 호출하면, 비동기 작업이 실행되고 즉시 응답을 반환
*/
@GetMapping("/asyncTest")
fun asyncTest(): String {
asyncService.doAsyncWork() // 비동기 작업 실행
return "비동기 작업이 시작되었습니다!"
}
}
위 코드가 어떻게 동작할까? 사용자가 /asyncTest API를 호출하면, 서버는 즉시 "비동기 작업이 시작되었습니다!" 라는 응답을 반환하고, 백그라운드에서 3초 후 "비동기 작업 완료!" 메세지를 콘솔에 출력한다. 순서대로 실행되는게 아니다.
Spring Boot와 Kotlin에서 간단한 메시지 큐를 구현해보자(RabbitMQ 사용)
1. build.gradle.kts에 의존성 추가
dependencies {
// RabbitMQ와 Spring Boot 연동을 위한 의존성
implementation("org.springframework.boot:spring-boot-starter-amqp")
// 기타 의존성...
}
2. application.properties에 RabbitMQ 설정
# RabbitMQ 서버 설정 (기본적으로 로컬에서 실행 중이라고 가정)
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
메세지 큐 서비스 구현
// MessageQueueService.kt
package com.example.demo.service
import org.springframework.amqp.rabbit.annotation.RabbitListener
import org.springframework.amqp.rabbit.core.RabbitTemplate
import org.springframework.stereotype.Service
/**
* MessageQueueService는 RabbitMQ를 사용하여 메시지 큐를 통한 비동기 처리를 구현한 서비스
*/
@Service
class MessageQueueService(val rabbitTemplate: RabbitTemplate) {
/**
* sendMessage 함수는 지정한 메시지를 "queue.tetris" 큐로 전송한다.
*/
fun sendMessage(message: String) {
rabbitTemplate.convertAndSend("queue.tetris", message)
}
/**
* receiveMessage 함수는 "queue.tetris" 큐를 구독하여 메시지를 수신한다.
* 메시지가 수신되면 콘솔에 출력하고, 필요한 추가 처리를 수행할 수 있다.
*/
@RabbitListener(queues = ["queue.tetris"])
fun receiveMessage(message: String) {
println("메시지 수신: $message")
// 메시지 처리 로직을 여기에 작성할 수 있다.
}
}
위에서 만든 메시지 큐를 활용하는 Controller 를 만들어보자.
// QueueController.kt
package com.example.demo.controller
import com.example.demo.service.MessageQueueService
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
/**
* QueueController는 메시지 큐를 통해 작업을 비동기적으로 처리하는 API를 제공
*/
@RestController
class QueueController(val messageQueueService: MessageQueueService) {
/**
* /queueTest 경로를 호출하면, "테트리스 블럭 이동"과 같은 작업 메시지를 큐에 전송한다.
*/
@GetMapping("/queueTest")
fun queueTest(): String {
// 메시지 큐에 메시지를 전송한다.
messageQueueService.sendMessage("테트리스 블럭 이동")
return "메시지가 큐에 전송되었습니다!"
}
}
사용자가 /queueTest API를 호출하면 메시지 "테트리스 블록 이동"이 RabbitMQ 큐에 전송되고, 해당 큐를 구독 중인 receiveMessage 메서드가 메시지를 수신하여 처리한다.
전체 아키텍쳐 다이어그램을 살펴보면
이런 식으로 표현할 수 있다. 사용자의 요청이 로드 밸런서를 통해 여러 서버에 분산되고, 각 서버는 캐싱, 비동기 처리. 메시지 큐, 데이터베이스를 활용해 안정적으로 응답을 제공하는 구조를 보여준다.
Gradle은 프로젝트를 빌드(컴파일, 테스트, 배포 등) 하는 도구이다. 그렇다면 또 빌드에 대해서 헷갈릴 수 있다. 빌드란? 소스코드 파일을 컴퓨터에서 실행할 수 있는 독립적인 형태로 변환하는 과정과 결과를 말한다. 즉, 개발자가 작성한 소스코드, 각각의 파일 자원 ( .xml, .jpa, .jpg, .properties)을 jvm이나 톰캣 같은 WAS가 인식할 수 있도록 패키징하는 과정 및 결과물을 빌드라고 한다.
다시 돌아와서 Gradle은 스프링 부트와 안드로이드에서 사용되며 빌드 속도가 Maven에 비해 10 ~ 100배 정도 빠르며, Java, C/C++, Python 등을 지원한다.
Gradle의 특징으로는
1. 가독성이 좋다.
2. 재사용에 용이
3. 구조적인 장점
4. 편리함
5. 멀티 프로젝트
등이 있다.
로그 관리를 위해서는 Spring Boot의 로깅 기능(기본적으로 Logback)을 사용한다.
build.gradle 파일에 아래와 같이 의존성을 추가한다.
plugins {
id("org.springframework.boot") version "2.7.5" // Spring Boot 버전
id("io.spring.dependency-management") version "1.0.15.RELEASE"
kotlin("jvm") version "1.6.21"
kotlin("plugin.spring") version "1.6.21"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
repositories {
mavenCentral()
}
dependencies {
// Spring Boot 기본 기능 (웹, 로깅 등)
implementation("org.springframework.boot:spring-boot-starter-web")
// Spring Boot AOP, 로깅 관련 기능 (Logback은 기본 포함)
implementation("org.springframework.boot:spring-boot-starter-aop")
// Kotlin 관련 의존성
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
로그 저장 : Logback과 파일 관리
Logback과 SLF4J에 대해 먼저 알아보자
SLF4J는 여러 로깅 라이브러리(Logback, Log4 J 등)를 추상화하여 사용하는 인터페이스이다.
Logback 은 Spring Boot에서 기본적으로 사용하는 로깅 프레임워크이다. Logback은 로그를 파일에 저장하거나 콘솔에 출력하는 등의 기능을 제공한다.
로그 설정 파일 : logback-spring.xml
Spring Boot 에서는 src/main/resource 폴더 안에 logbak-spring.xml 파일을 만들면, 로깅의 형식, 저장 위치, 파일 회전(일정 시간마다 새 파일로 저장)등을 설정할 수 있다.
<configuration>
<!-- FILE appender: 로그를 파일에 저장 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 로그 파일 저장 위치 -->
<file>logs/app.log</file>
<!-- 로그 파일 회전 정책: 매일 새 파일 생성하고, 30일간 보관 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 로그 출력 형식: 날짜, 스레드, 로그 레벨, 로거 이름, 메시지 -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 콘솔에도 로그 출력 (옵션) -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 루트 로거 설정: 기본 로그 레벨을 INFO로 설정 -->
<root level="INFO">
<appender-ref ref="FILE" />
<appender-ref ref="CONSOLE" />
</root>
</configuration>
위 설정은
logs/app.log 라는 파일에 로그를 기록한다.
매일 새로운 로그 파일을 만들고, 30일 지난 로그는 삭제한다
콘솔에도 로그가 출력되어, 개발 중에 쉽게 확인할 수 있다.
이렇게 이해하면 된다.
위와 같이 저장한 로그 파일에서 로그를 조회하기 위한 방법은 몇 가지가 있다.
(1) 파일 탐색기나 텍스트 에디터 사용
로그 파일을 텍스트 에디터(VS Code, Notepad++ 등)로 열어 직접 내용을 확인하는 방법
(2) 터미널 명령어 사용
tail : 최신 로그 및 몇 줄을 확인할때 사용한다. ( 예 : tail -f app.log )
grep : 특정 키워드(ERROR, 특정 메서드 이름)를 검색할 때 사용한다. ( 예 : grep "ERROR" logs/app.log )
중앙 집중식 로그 관리 시스템
ELK 스택 : Elasticsearch, Logstash, Kibana 를 사용하면, 웹 인터페이스에서 로그를 필터링 하고 조회할 수 있따.
이 방법은 여러 서버의 로그를 한 곳에서 모아 볼 때 유용하다.
ELK 스택의 구성
Elasticsearch: 로그 데이터를 저장하고, 빠르게 검색할 수 있는 데이터베이스 역할을 합니다.
Logstash/Filebeat: 여러 서버에서 로그 파일을 수집하여 Elasticsearch로 전송합니다.
Kibana: Elasticsearch에 저장된 로그 데이터를 시각화하고, 웹 인터페이스에서 검색할 수 있도록 해 줍니다.
ELK 스택의 장점
중앙 집중식 조회: 모든 서버의 로그를 한 곳에서 볼 수 있습니다.
실시간 검색 및 필터링: 원하는 키워드나 시간대에 맞춰 로그를 빠르게 검색할 수 있습니다.
대시보드: 시각화 도구를 사용해 서버 상태를 모니터링하고 문제를 빠르게 파악할 수 있습니다.
Spring Boot는 빠르게 프로토타입을 만글고, 반복적인 설정 작업 없이 비즈니스 로직에 집중할 수 있게
Auto Configuration (자동 구성) 기능을 제공한다.
한번 Spring Boot Auto Configuration 기본 개념과 동작 원리, 활용예제에 대해서 알아보자.
Auto Configuration이란 무엇인가?
Auto Configuration은 개발자가 매번 복잡한 설정 파일이나 보일러플레이트 코드를 작성하지 않아도, 애플리케이션이 실행될 때 필요한 설정을 자동으로 구성해주는 기능이다. Spring Boot는 클래스패스에 포함된 라이브러리와 프로젝트 설정을 기반으로, 애플리케이션에 필요한(Bean)들을 자동으로 등록한다.
비유를 들어보자면 새로운 스마트폰을 구입했다고 생각해보자, 별도의 복잡한 설정 없이 기본 앱들과 기능들이 이미 준비되어 있는 상태일것이다. Spring Boot 의 Auto Configuration은 바로 그런 스마트폰과 같이, 개발자가 직접 설정하지 않아도 "기본값"으로 모든 것이 준비되도록 도와준다.
왜 Auto Configuration이 필요할까?
전통적인 Spring 애플리케이션은 XML이나 자바 기반의 설정 파일을 통해 수많은 설정을 해야 한다.( 정말 정말 복잡하고 귀찮은 작업이다..) 이러한 작업은 시간도 오래걸리고, 실수로 인한 오류가 발생하기 쉽다.
그렇기 때문에 Spring Boot 의 Auto Configuration 은 장점을 갖는다.
개발 속도 향상: 복잡한 설정을 자동으로 처리하여 개발자가 비즈니스 로직에 집중할 수 있다.
일관성 있는 설정: 기본적인 설정이 표준화되어 있어, 팀 내에서 일관된 개발 환경을 유지할 수 있다.
쉬운 시작: 처음 프로젝트를 시작할 때 최소한의 설정만으로도 애플리케이션을 실행할 수 있다.
Auto Configuration의 동작 원리
Spring Boot의 Auto Configuration은 내부적으로 여러 가지 메커니즘과 어노테이션을 사용해 동작한다.
@EnableAutoConfiguration / @SpringBootApplication
Spring Boot 애플리케이션의 진입점에는 보통 @SpringBootApplication 어노테이션이 붙는다. 이 어노테이션은
@EnableAutoConfiguration, @ComponentScan, @Configuration 등 여러 어노테이션을 합친 축약 표현이다.
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
@EnableAutoConfiguration : 해당 어노테이션은 Spring Boot에게 애플리케이션 시작 시 자동 구성을 활성화하라고 지시한다.
Auto Configuration은 클래스패스에 있는 라이브러리와 설정 파일을 확인한 후, 필요한 빈들을 등록한다.
이를 위해 조건부(Conditional) 어노테이션들이 사용된다. 대표적으로 @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty 등이 있다.
예를 들어, H2 데이터베이스 라이브러리가 클래스패스에 있다면, Spring Boot는 자동으로 H2 데이터베이스 관련 빈을 등록한다.
만약 사용자가 직접 데이터 소스(DataSource)를 정의했다면, @ConditionalOnMissingBean 어노테이션 덕분에 자동 구성은 이를 무시하고 사용자가 정의한 설정을 우선시한다.
@Configuration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
// 기본 H2 데이터베이스 설정을 반환
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
Auto Configuration 파일의 구조
Spring Boot의 Auto Configuration 설정은 보통 spring.factories 파일에 정의되어 있다. 이 파일은 각 라이브러리별로 어떤 Auto Configuration 클래스를 적용할지 명시해 두며, 애플리케이션이 시작될 때 해당 클래스들이 로드된다.