자바 스프링 프레임워크 기초: IoC/DI부터 REST API까지 한 번에 잡기
자바 스프링 프레임워크 기초: IoC/DI부터 REST API까지 한 번에 잡기
스프링(Spring)은 “자바로 웹/서버를 만들 때 매번 반복되는 일을 줄이고”, “구조적으로 안전하게 확장 가능한 코드”를 만들도록 도와주는 프레임워크입니다. 이번 글은 스프링의 핵심 개념(IoC/DI/Bean)부터 Spring Boot로 REST API를 구성하는 기본 흐름까지, 처음 시작하는 개발자가 실무형으로 이해할 수 있게 정리합니다.
1) 스프링이 해결하는 문제: “반복, 결합도, 테스트 어려움”
1-1. 반복되는 인프라 코드 감소
순수 자바로 서버를 만들면 설정/객체 생성/연결/예외 처리/트랜잭션/보안 같은 “기반 공사”에 시간이 많이 들어갑니다. 스프링은 이 기반 공사를 프레임워크가 대신 처리하고, 개발자는 비즈니스 로직에 집중하게 합니다.
1-2. 클래스 간 결합도(Dependency) 낮추기
규모가 커질수록 “A가 B를 new로 직접 생성”하는 구조는 변경에 약해지고, 테스트도 어려워집니다. 스프링은 객체 생성과 연결을 컨테이너가 관리하며, 필요한 곳에 “주입(Injection)”해 결합도를 낮춥니다.
1-3. 테스트 가능한 구조로 유도
DI 기반 설계는 Mock(가짜 객체)로 대체가 쉬워 테스트가 쉬워지고, 계층 구조(Controller → Service → Repository)로 책임이 분리되면서 유지보수가 편해집니다.
2) 스프링 핵심 개념 3가지: IoC, DI, Bean/Container
2-1. IoC (Inversion of Control, 제어의 역전)
기존 방식: 개발자가 객체 생명주기(생성/연결/파기)를 직접 통제합니다.
스프링 방식: 컨테이너(ApplicationContext)가 객체 생명주기와 연결을 관리합니다. 이게 “제어가 개발자에서 프레임워크로 넘어갔다”는 의미입니다.
2-2. DI (Dependency Injection, 의존성 주입)
“필요한 객체를 직접 만드는 것(new)”이 아니라, 외부(컨테이너)가 만들어서 넣어주는 방식입니다. 일반적으로 생성자 주입이 권장됩니다.
// ❌ 나쁜 예: 직접 생성(결합도 ↑) public class OrderService { private final PaymentClient paymentClient = new PaymentClient(); } // ✅ 좋은 예: 생성자 주입(결합도 ↓, 테스트 쉬움) public class OrderService { private final PaymentClient paymentClient; public OrderService(PaymentClient paymentClient) { this.paymentClient = paymentClient; } }
2-3. Bean과 Container(ApplicationContext)
Bean은 스프링 컨테이너가 관리하는 객체입니다. 컨테이너는 Bean을 등록하고, 필요한 곳에 주입하며, 싱글톤/스코프/생명주기 등을 관리합니다.
3) Spring vs Spring Boot: 초보는 Boot로 시작하는 이유
3-1. Spring Framework
핵심 기능(IoC/DI, AOP, 트랜잭션, MVC 등)을 제공하는 “기반 프레임워크”입니다.
3-2. Spring Boot
스프링 프로젝트를 빠르게 시작하도록 기본 설정과 자동 구성(Auto Configuration), Starter 의존성, 내장 서버 등을 제공하는 “가속기”입니다. 실무에서도 신규 프로젝트는 대부분 Boot로 시작합니다.
4) 프로젝트 구조(기본): Controller / Service / Repository
스프링에서 가장 흔한 기본 구조는 다음과 같습니다.
- Controller: HTTP 요청/응답 처리(REST API 엔드포인트)
- Service: 비즈니스 로직(규칙/정책/검증)
- Repository: 데이터 접근(JPA/DB, 외부 저장소)
이 구조를 지키면 “역할이 섞여서 커지는 코드”를 방지하고, 테스트도 계층별로 나누기 쉬워집니다.
5) 실습: 가장 단순한 REST API 만들기
5-1. 예제 목표
- GET /api/health : 서버 상태 확인
- GET /api/todos : Todo 목록 조회
- POST /api/todos : Todo 생성
5-2. Controller (요청/응답)
import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api")
public class ApiController {
private final TodoService todoService;
// ✅ 생성자 주입
public ApiController(TodoService todoService) {
this.todoService = todoService;
}
@GetMapping("/health")
public ResponseEntity health() {
return ResponseEntity.ok("OK");
}
@GetMapping("/todos")
public ResponseEntity<List> list() {
return ResponseEntity.ok(todoService.getTodos());
}
@PostMapping("/todos")
public ResponseEntity create(@RequestBody CreateTodoRequest req) {
return ResponseEntity.ok(todoService.create(req));
}
}
5-3. DTO (요청/응답 모델)
public record CreateTodoRequest(String title) {}
public record TodoDto(Long id, String title, boolean done) {}
5-4. Service (비즈니스 로직)
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TodoService {
private final TodoRepository todoRepository;
public TodoService(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
public List getTodos() {
return todoRepository.findAll();
}
public TodoDto create(CreateTodoRequest req) {
if (req == null || req.title() == null || req.title().isBlank()) {
throw new IllegalArgumentException("title은 비어있을 수 없습니다.");
}
return todoRepository.save(req.title().trim());
}
}
5-5. Repository (초간단 in-memory 버전)
처음엔 DB 없이도 흐름을 익히기 위해 메모리 저장소로 시작할 수 있습니다.
import org.springframework.stereotype.Repository; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; @Repository public class TodoRepository { private final List<TodoDto> store = new ArrayList<>(); private final AtomicLong seq = new AtomicLong(0); public List<TodoDto> findAll() { return new ArrayList<>(store); } public TodoDto save(String title) { TodoDto todo = new TodoDto(seq.incrementAndGet(), title, false); store.add(todo); return todo; } }
6) 스프링 어노테이션(처음에 꼭 알아야 할 것)
6-1. 컴포넌트 스캔과 등록
@RestController: REST API 컨트롤러(반환이 JSON 중심)@Controller: 뷰(템플릿) 기반 MVC 컨트롤러@Service: 서비스 계층(비즈니스 로직)@Repository: 저장소 계층(데이터 접근)@Component: 일반 컴포넌트
6-2. 요청 매핑
@RequestMapping: 공통 경로/메서드 지정@GetMapping,@PostMapping,@PutMapping,@DeleteMapping@PathVariable,@RequestParam,@RequestBody
6-3. 설정과 환경
application.yml또는application.properties로 설정 관리profiles로 dev/prod 환경 분리
7) 트랜잭션(Transaction) 기초: 데이터는 “한 번에” 안전하게
DB가 들어가면 “중간에 실패했을 때 일부만 반영되는 문제”를 막아야 합니다. 스프링은 @Transactional로 트랜잭션 경계를 잡아 “전부 성공하면 커밋, 하나라도 실패하면 롤백”하게 만들 수 있습니다.
import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class OrderService { private final OrderRepository orderRepository; public OrderService(OrderRepository orderRepository) { this.orderRepository = orderRepository; } @Transactional public void placeOrder() { // 1) 주문 저장 // 2) 결제 처리 // 3) 재고 차감 // 중간에 예외 발생 시 전체 롤백 } }
※ 초보 단계에서는 “트랜잭션은 서비스 레이어에서 묶는다” 정도만 확실히 잡아두면 충분합니다.
8) 예외 처리(필수): 사용자에게 친절한 에러 응답 만들기
예외를 그대로 던지면 500 에러만 나오고 원인 파악이 힘들어집니다. 기본은 @RestControllerAdvice로 공통 예외 응답을 만드는 것입니다.
import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity<ErrorResponse> handleBadRequest(IllegalArgumentException e) { return ResponseEntity.badRequest().body(new ErrorResponse("BAD_REQUEST", e.getMessage())); } public record ErrorResponse(String code, String message) {} }
9) 테스트 기초: Controller 테스트는 MockMvc로 시작
스프링은 테스트 도구가 잘 갖춰져 있습니다. API가 깨지지 않도록 최소한의 테스트를 붙이면 유지보수 난이도가 크게 내려갑니다.
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @AutoConfigureMockMvc class ApiControllerTest { @Autowired MockMvc mockMvc; @Test void health_returns_ok() throws Exception { mockMvc.perform(get("/api/health")) .andExpect(status().isOk()); } }
10) 입문자가 자주 하는 실수 TOP 7 (디버깅 체크리스트)
- DI 대신 new로 객체 생성 → 스프링이 관리하는 Bean이 아니어서 설정/프록시/@Transactional이 안 먹는 경우가 생깁니다.
- 어노테이션 위치 실수 → @Service/@Repository 누락, 패키지 스캔 범위 밖에 클래스가 있는 경우
- Controller에 비즈니스 로직을 몰아넣기 → 유지보수 지옥의 시작
- DTO 없이 Entity를 그대로 응답 → 보안/확장/성능 문제가 생기기 쉽습니다.
- 예외 처리 없이 500만 반환 → 사용자 경험/운영 효율 급락
- 설정 파일(dev/prod) 분리 안 함 → 배포 환경에서 장애가 나기 쉬움
- 로그 없이 감으로 디버깅 → 최소한 요청/에러 로그는 남겨야 합니다.
11) 학습 로드맵: 다음 단계는 이렇게 가면 빠릅니다
- 1단계: IoC/DI, Controller/Service/Repository 구조 익히기
- 2단계: Spring Data JPA로 DB CRUD 연결
- 3단계: Validation(@Valid), 예외 처리 공통화
- 4단계: Spring Security로 인증/인가 기초
- 5단계: 로그/모니터링(Actuator), 성능 튜닝, 캐시(Redis)
여기까지 이해했다면, “스프링으로 웹 서버를 만들 때의 기본 골격”은 이미 잡힌 상태입니다. 다음 글에서는 Spring Data JPA로 실제 DB 연동과 트랜잭션 설계를 더 실무적으로 이어가면 좋습니다.
관련 키워드 태그 (10개)
#Spring #SpringBoot #Java #IoC #DI #Bean #RESTAPI #MVC #JPA #Backend
Meta Description (160자)
자바 스프링 프레임워크 기초를 IoC/DI/Bean 개념부터 Controller·Service·Repository 구조, REST API 실습, 예외 처리·테스트까지 실무 흐름으로 정리했습니다.
::contentReference[oaicite:0]{index=0}
'it' 카테고리의 다른 글
| Node.js 서버 이메일 인증 구현 완성본 (Express + Prisma + PostgreSQL + Nodemailer) (0) | 2026.02.03 |
|---|---|
| 비전공자 네트워크 공부 독학 로드맵: “개념 → 실습 → 트러블슈팅”으로 끝내기 (0) | 2026.02.02 |
| 라즈베리파이 5 NAS 구축 가이드 (집에서 쓰는 실전형 NAS) (1) | 2026.02.01 |
| 가비아에서 Node.js 호스팅하는 방법: 컨테이너호스팅으로 배포부터 운영까지(실전 체크리스트) (0) | 2026.02.01 |
| 라즈베리파이 서버 구축 (2026 최신판): 설치부터 보안·운영까지 ‘실전’ 한 번에 끝내기 (0) | 2026.01.31 |
| OSI 7계층 쉽게 이해하기: “인터넷이 되는 이유”를 한 번에 정리 (0) | 2026.01.30 |
| 웹사이트 호스팅 비용 및 방법 (2026년 기준): 개발자가 “돈 새는 지점”부터 막는 실전 가이드 (0) | 2026.01.29 |
| 백엔드 개발자 로드맵 (2026): “뭘 먼저 배워야 하지?”를 끝내는 단계별 학습 지도 (0) | 2026.01.29 |