실제 예외 처리 흐름

물론입니다! 아래는 Spring Boot REST API 프로젝트에서 Controller → Service → Exception 처리까지 전체 흐름을 보여주는 실제 사용 예시입니다. 주석과 함께 자세히 설명드릴게요.


✅ 예시 시나리오

사용자 등록 API에서 중복 이메일이 들어오면 DuplicateEmailException을 발생시키고, 이를 @RestControllerAdvice에서 처리합니다.


1. 사용자 정의 예외 클래스 (중복 이메일 예외)

package com.example.exception;

public class DuplicateEmailException extends RuntimeException {
    public DuplicateEmailException(String email) {
        super("이미 사용 중인 이메일입니다: " + email);
    }
}

2. 서비스 계층

package com.example.service;

import com.example.exception.DuplicateEmailException;
import com.example.model.User;
import com.example.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    public void registerUser(String email, String name) {
        if (userRepository.existsByEmail(email)) {
            throw new DuplicateEmailException(email);
        }

        User user = new User(email, name);
        userRepository.save(user);
    }
}

3. 컨트롤러 계층

package com.example.controller;

import com.example.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @PostMapping("/register")
    public ResponseEntity<String> register(@RequestParam String email, @RequestParam String name) {
        userService.registerUser(email, name);
        return ResponseEntity.ok("회원가입 성공");
    }
}

4. 공통 예외 처리기 (@RestControllerAdvice)

package com.example.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;

@RestControllerAdvice
public class RestGlobalExceptionHandler {

    @ExceptionHandler(DuplicateEmailException.class)
    public ResponseEntity<ErrorResponse> handleDuplicateEmail(DuplicateEmailException ex, HttpServletRequest request) {
        ErrorResponse response = new ErrorResponse(
                LocalDateTime.now(),
                HttpStatus.CONFLICT.value(),
                "Duplicate Email",
                ex.getMessage(),
                request.getRequestURI()
        );
        return new ResponseEntity<>(response, HttpStatus.CONFLICT);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleAll(Exception ex, HttpServletRequest request) {
        ErrorResponse response = new ErrorResponse(
                LocalDateTime.now(),
                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                "Internal Server Error",
                "시스템에 문제가 발생했습니다.",
                request.getRequestURI()
        );
        return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

🔁 실행 결과 예시

✅ 성공 시

POST /api/users/register?email=new@example.com&name=홍길동
200 OK
"회원가입 성공"

❌ 중복 이메일 예외

POST /api/users/register?email=test@example.com&name=홍길동
409 Conflict
{
  "timestamp": "2025-06-26T11:25:00.123",
  "status": 409,
  "error": "Duplicate Email",
  "message": "이미 사용 중인 이메일입니다: test@example.com",
  "path": "/api/users/register"
}

이 예시는 실무에서 매우 자주 사용하는 구조입니다.

✅ 1. @RestControllerAdvice가 하는 일

  • @RestControllerAdvice는 전체 애플리케이션에서 발생한 예외를 가로채어 처리해주는 글로벌 예외 처리기입니다.

  • @ExceptionHandler(SomeException.class)와 함께 사용되어, 특정 예외 타입이 발생했을 때 자동으로 해당 메서드로 우회되며 처리됩니다.


✅ 2. 왜 Controller에서 throws가 없어도 동작하는가?

  • throws체크 예외 (checked exception) 처리 시 컴파일러가 요구합니다.

  • 하지만 대부분의 애플리케이션 예외 (RuntimeException 계열)는 **언체크 예외 (unchecked exception)**이며, throws를 명시하지 않아도 됩니다.

  • 따라서, 예외가 Controller 내부에서 발생하면 자동으로 컨트롤러 바깥으로 전파되고, @RestControllerAdvice에서 캐치하여 응답합니다.

// throws 없어도 런타임 예외는 밖으로 던져짐
@PostMapping("/register")
public ResponseEntity<String> register(@RequestParam String email) {
    userService.registerUser(email); // 내부에서 런타임 예외 발생 가능
    return ResponseEntity.ok("회원가입 성공");
}

✅ 3. 왜 "마지막에 예외 처리"라고 말하는가?

  • @RestControllerAdvice최종 예외 처리자입니다. → 즉, 중간에서 try-catch 하지 않는 이상, 예외는 계속 위로 올라가 @RestControllerAdvice에서 처리됩니다.

  • Spring MVC가 요청을 처리하는 DispatcherServlet → Controller → Service → 예외 발생 순서에서, 예외가 어디서 터지든 결국 @RestControllerAdvice로 모이게 되는 구조입니다.


✅ 요약

질문
답변

@RestControllerAdvice 때문에 예외가 마지막에 처리됨?

✅ 네, 전역 예외 핸들러 역할

Controller에 throws 없어도 되는 이유는?

✅ RuntimeException은 언체크 예외라 명시적 throws 불필요

어디서 던져도 @RestControllerAdvice가 잡나?

✅ 중간에 catch 안 하면 모두 거기서 처리됨

Last updated