it ·

자바 스프링 프레임워크 기초: IoC/DI부터 REST API까지 한 번에 잡기

728x90
반응형
Java Spring Framework 대표 이미지
대표 이미지: Java & Spring 개발 콘셉트

자바 스프링 프레임워크 기초: 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 (디버깅 체크리스트)

  1. DI 대신 new로 객체 생성 → 스프링이 관리하는 Bean이 아니어서 설정/프록시/@Transactional이 안 먹는 경우가 생깁니다.
  2. 어노테이션 위치 실수 → @Service/@Repository 누락, 패키지 스캔 범위 밖에 클래스가 있는 경우
  3. Controller에 비즈니스 로직을 몰아넣기 → 유지보수 지옥의 시작
  4. DTO 없이 Entity를 그대로 응답 → 보안/확장/성능 문제가 생기기 쉽습니다.
  5. 예외 처리 없이 500만 반환 → 사용자 경험/운영 효율 급락
  6. 설정 파일(dev/prod) 분리 안 함 → 배포 환경에서 장애가 나기 쉬움
  7. 로그 없이 감으로 디버깅 → 최소한 요청/에러 로그는 남겨야 합니다.

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}

728x90
반응형