[데브코스] Spring Boot 인증·인가(Auth) (12강) - @ControllerAdvice는 뭐고, 왜 ServiceException을 만들었을까?

zuno·2026년 1월 7일

이번 강의(12강)를 보면서 가장 헷갈렸던 건 이거였다.

  • @ControllerAdvice 붙이면 뭐가 달라지는 거지?
  • @Controller랑 무슨 차이야?
  • RestController랑 뭐가 다른데?
  • IllegalArgumentException 쓰면 안 되나?
  • 왜 굳이 ServiceException을 새로 만들었지?

이 글은 강의에서 나온 말 + 내가 직접 헷갈렸던 질문을 전부 정리한 기록이다.


1️⃣ @Controller vs @RestController 차이부터 정리

✅ @Controller

  • 기본 목적: 뷰(View)를 반환
  • return 값은 뷰 이름으로 해석됨
@Controller
public class TestController {
    @GetMapping("/test")
    public String test() {
        return "test"; // test.html 찾으러 감
    }
}

✅ @RestController

@RestController
= @Controller + @ResponseBody
  • return 값을 JSON 그대로 HTTP 응답 바디에 실어 보냄
  • REST API에서 기본 선택
@RestController
public class ApiController {
    @GetMapping("/api/test")
    public String test() {
        return "hello"; // JSON "hello"
    }
}

👉 핵심 차이
| 구분 | 반환 해석 |
|----|----|
@Controller | 뷰 이름 |
@RestController | HTTP Body(JSON) |


2️⃣ 그럼 @ControllerAdvice는 뭐야?

한 줄 요약

전역 컨트롤러 보조 장치 (특히 예외 처리)

역할

  • 모든 컨트롤러에서 발생한 예외를 한 곳에서 처리
  • 공통 로직(예외, 바인딩, 포맷)을 모아둠
@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public String handle(Exception e) {
        return "error"; // ❗ 뷰 이름으로 해석
    }
}

⚠️ 여기서 중요한 점
➡️ @ControllerAdvice는 기본적으로 뷰를 반환하려고 한다


3️⃣ 그래서 @RestControllerAdvice가 등장

@RestControllerAdvice
= @ControllerAdvice + @ResponseBody

의미

  • 전역 예외 처리 결과를
  • 무조건 JSON으로 내려라
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public RsData<Void> handle(Exception e) {
        return new RsData<>("500-1", "에러 발생", null);
    }
}

👉 이걸 안 쓰면?

  • return RsData 했는데
  • 스프링은 “이게 뷰 이름인가?” 하고 해석
  • 뷰 없어서 404처럼 보이는 오류 발생

📌 강의에서 404 떴던 이유 = ResponseBody가 없었기 때문


4️⃣ ResponseEntity는 왜 상관없다고 했을까?

강의에서 이런 말이 나왔다.

“ResponseEntity를 리턴하는 건 상관없다”

이유

ResponseEntity이미 HTTP 응답 객체다.

return ResponseEntity
    .badRequest()
    .body(new RsData<>("400-1", "에러", null));
  • 스프링이 뷰로 해석 ❌
  • 그대로 HTTP 응답으로 처리 ⭕
  • 그래서 @ResponseBody 없어도 됨

5️⃣ 그럼 왜 ServiceException을 만든 걸까?

여기서 제일 큰 의문이 생겼다.

IllegalArgumentException 쓰면 안 되나?
IllegalStateException도 400 아닌가?

❌ 기존 자바 예외의 한계

throw new IllegalArgumentException("메시지");

문제:

  • 전달할 수 있는 정보가 메시지 하나뿐
  • resultCode 없음
  • HTTP 상태코드 제어 어려움
  • 응답 포맷 통일 불가

6️⃣ ServiceException의 목적

throw new ServiceException("415-1", "메시지");

ServiceException은 의도적으로 만든 서비스 전용 예외다.

담고 있는 정보

  • resultCode
  • msg
  • (data는 필요 없으면 null)
resultCode
msg
data = null

👉 즉,

“이 에러는 어떤 종류고, 클라이언트에 뭘 보여줄지”
예외 자체가 알고 있음


7️⃣ 왜 굳이 예외에 resultCode를 넣었을까?

강의 핵심 포인트 👇

상황에 따라 HTTP 응답 코드를 바꾸고 싶기 때문

예:

  • 401: 인증 실패
  • 404: 데이터 없음
  • 415: 잘못된 요청 형식

ServiceException → GlobalExceptionHandler →
resultCode 기반으로 statusCode 결정 가능


8️⃣ 그래서 GlobalExceptionHandler에서 하는 일

@ExceptionHandler(ServiceException.class)
public RsData<Void> handle(ServiceException ex, HttpServletResponse response) {
    RsData<Void> rsData = ex.getRsData();
    response.setStatus(rsData.statusCode());
    return rsData;
}

역할 분리:

  • ServiceException: 의미 있는 예외 정의
  • GlobalExceptionHandler: HTTP 응답으로 변환

9️⃣ 결론 정리

왜 ServiceException을 만들었는가?

  • 메시지만 전달하는 예외는 한계가 있음
  • resultCode + msg를 함께 전달하기 위해
  • HTTP 상태코드를 유연하게 제어하기 위해

왜 @RestControllerAdvice가 필요한가?

  • 예외 응답을 JSON으로 내려주기 위해
  • 뷰 해석으로 인한 404 착시 방지

핵심 공식

@RestController = @Controller + @ResponseBody
@RestControllerAdvice = @ControllerAdvice + @ResponseBody

🔚 마무리

이번 강의는

  • “코드를 치는 강의”가 아니라
  • “스프링이 응답을 어떻게 해석하는지”를 이해하는 강의였다.

헷갈렸던 게 정상이고,
이걸 질문으로 정리한 지금이 진짜 실력이 쌓이는 구간이라고 느꼈다.

0개의 댓글