Java 26일 코스 - Day 24: Spring Boot 입문

Day 24: Spring Boot 입문

Spring Boot는 Java 웹 애플리케이션 개발의 사실상 표준 프레임워크입니다. 복잡한 설정 없이 빠르게 애플리케이션을 만들 수 있습니다. 레스토랑에 비유하면, Spring은 주방 장비 세트이고 Spring Boot는 모든 장비가 이미 세팅된 턴키 주방입니다.

Spring Boot 프로젝트 시작

Spring Initializr(https://start.spring.io)로 프로젝트를 생성하거나 직접 설정합니다.

// build.gradle.kts
/*
plugins {
    java
    id("org.springframework.boot") version "3.2.0"
    id("io.spring.dependency-management") version "1.1.4"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

java {
    sourceCompatibility = JavaVersion.VERSION_17
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.test {
    useJUnitPlatform()
}
*/

// 메인 애플리케이션 클래스
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication // 자동 설정 + 컴포넌트 스캔 + 설정 클래스
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
        // 내장 톰캣 서버가 8080 포트에서 시작됨
    }
}

IoC/DI (제어의 역전과 의존성 주입)

Spring의 핵심 원리를 이해합니다. 객체 생성과 관리를 개발자가 아닌 Spring 컨테이너가 담당합니다.

import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

// 인터페이스 정의
interface GreetingService {
    String greet(String name);
}

// 구현체 1: @Service로 Spring Bean 등록
@Service
class KoreanGreetingService implements GreetingService {
    @Override
    public String greet(String name) {
        return name + "님, 안녕하세요!";
    }
}

// 구현체 2 (필요 시 @Primary나 @Qualifier로 선택)
// @Service
// class EnglishGreetingService implements GreetingService {
//     @Override
//     public String greet(String name) {
//         return "Hello, " + name + "!";
//     }
// }

// 의존성 주입 받는 클래스
@Component
class WelcomeProcessor {
    private final GreetingService greetingService;

    // 생성자 주입 (권장 방식)
    // @Autowired 생략 가능 (생성자가 하나일 때)
    WelcomeProcessor(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    String processWelcome(String name) {
        String greeting = greetingService.greet(name);
        return "[환영] " + greeting;
    }
}

// DI의 장점:
// 1. 느슨한 결합: 구현체 교체가 쉬움
// 2. 테스트 용이: Mock 객체 주입 가능
// 3. 코드 재사용: 같은 빈을 여러 곳에서 사용

간단한 웹 컨트롤러

HTTP 요청을 처리하는 컨트롤러를 만듭니다.

import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import java.time.LocalDateTime;
import java.util.Map;

@RestController // JSON 응답을 반환하는 컨트롤러
@RequestMapping("/api") // 공통 경로 접두사
class HelloController {

    private final GreetingService greetingService;

    HelloController(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    // GET /api/hello
    @GetMapping("/hello")
    Map<String, Object> hello() {
        return Map.of(
            "message", "Hello, Spring Boot!",
            "timestamp", LocalDateTime.now().toString()
        );
    }

    // GET /api/hello/홍길동
    @GetMapping("/hello/{name}")
    Map<String, String> helloName(@PathVariable String name) {
        return Map.of(
            "greeting", greetingService.greet(name)
        );
    }

    // GET /api/search?keyword=java&page=1
    @GetMapping("/search")
    Map<String, Object> search(
            @RequestParam String keyword,
            @RequestParam(defaultValue = "1") int page) {
        return Map.of(
            "keyword", keyword,
            "page", page,
            "results", "'" + keyword + "' 검색 결과 (페이지 " + page + ")"
        );
    }

    // POST /api/echo
    @PostMapping("/echo")
    ResponseEntity<Map<String, Object>> echo(@RequestBody Map<String, Object> body) {
        return ResponseEntity.ok(Map.of(
            "received", body,
            "echo", true,
            "timestamp", LocalDateTime.now().toString()
        ));
    }
}

application.properties 설정

Spring Boot 애플리케이션 설정 파일입니다.

// src/main/resources/application.properties
/*
# 서버 설정
server.port=8080
server.servlet.context-path=/

# 로깅 설정
logging.level.root=INFO
logging.level.com.example=DEBUG

# JSON 설정
spring.jackson.serialization.indent-output=true
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

# 데이터베이스 (H2 인메모리)
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

# H2 콘솔 활성화 (개발용)
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
*/

// application.yml 형식 (대안):
/*
server:
  port: 8080

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver

logging:
  level:
    root: INFO
    com.example: DEBUG
*/

// 프로파일별 설정:
// application-dev.properties  (개발)
// application-prod.properties (운영)
// 활성화: spring.profiles.active=dev

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
class AppConfig {
    @Value("${server.port:8080}")
    private int port;

    @Value("${app.name:MyApp}")
    private String appName;

    void printConfig() {
        System.out.println("포트: " + port);
        System.out.println("앱 이름: " + appName);
    }
}

오늘의 연습문제

  1. 프로필 API: /api/profile 엔드포인트를 만드세요. GET은 프로필 정보(이름, 이메일, 자기소개)를 반환하고, POST는 프로필을 업데이트합니다. 데이터는 메모리(Map)에 저장하세요.

  2. 환율 계산기 API: /api/exchange?from=USD&to=KRW&amount=100 형태의 GET 요청을 처리하는 컨트롤러를 만드세요. 환율 데이터는 Map으로 하드코딩하고, 없는 통화 요청 시 적절한 에러 응답을 반환하세요.

  3. DI 실습: NotificationService 인터페이스와 EmailNotification, SmsNotification 구현체를 만들고, @Qualifier로 원하는 구현체를 주입받는 컨트롤러를 작성하세요.

이 글이 도움이 되었나요?