[Spring Boot] 49. 스프링부트 블로그 v2 (JPA) (19) 예외처리

김주희's avatar
Apr 09, 2025
[Spring Boot] 49. 스프링부트 블로그 v2 (JPA) (19) 예외처리

0. 패키지 구조

notion image

1. 예외처리

여태껏 스프링부트로 서버를 만들면서 exception은 미리 잡아 채서 터트리는 방식으로 처리하고 있었다.
그러나 스프링부트에서는 서버의 가장 앞단인 DS에서 원래 모든 exception을 전부 잡아채서 처리할 수 있도록 지원한다. 이를 적용해보자
 

2. 401 : 인증 안 됨

http 상태코드 중 401은 인증이 필요한 기능을 인증 없이 시도할 경우 발생한다.

1. Exception401

Exception은 모든 에러의 부모이고 모든 에러가 한 곳으로 들어오면 처리가 힘들기 때문에 customException을 만든다. Exception401은 내가 만든 Exception으로 RuntimeException 클래스를 상속하였기 때문에 Exception 중 하나로서 사용 가능하다.
package shop.mtcoding.blog._core.error.ex; // 내가 만든 Exception -> extends 해야 진짜 Exception이 됨 public class Exception401 extends RuntimeException { public Exception401(String message) { super(message); } }
 

2. GlobalExceptionHandler - ex401()

  1. @RestControllerAdvice : 모든 @RestController 에서 발생하는 예외를 전역적으로 처리하기 위한 클래스에 붙인다. 모든 에러가 해당 클래스로 들어와서 처리되고 응답이 json으로 반환된다. 즉 데이터를 반환한다. ( = @ControllerAdvice + @ResponseBody)
  1. @ControllerAdvice : @RestControllerAdvice와 비슷한 기능을 하지만 View 즉, file을 return한다.
  1. @ExceptionHandler : 괄호 안에 정의된 Exception 클래스인 특정한 예외가 발생하였을 때 그 예외를 처리할 메서드를 정의한다.
  1. ex401 메서드는 @RestControllerAdvice 가 적용된 클래스의 메서드이므로 데이터를 반환해야한다. 따라서 에러 메세지를 알림창으로 띄우기 위해서 String 타입의 html이라는 변수에 script 태그를 아예 집어넣어서 html이라는 변수를 반환하도록 처리한다. 따라서 JS로 원하는 메세지를 alert 하고 401은 인증이 안 되었기 때문에 발생하는 문제이므로 UX를 고려하여 로그인 페이지로 이동할 수 있도록 한다.
package shop.mtcoding.blog._core.error; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import shop.mtcoding.blog._core.error.ex.Exception401; import shop.mtcoding.blog._core.error.ex.ExceptionApi401; import shop.mtcoding.blog._core.util.Resp; @RestControllerAdvice // 모든 에러가 이 클래스로 들어온다. data return (ControllerAdvice : file return) public class GlobalExceptionHandler { // 401 : 인증 안 됨 @ExceptionHandler(Exception401.class) // Exception은 모든 에러의 부모 -> 모든 에러가 여기로 들어오면 힘들기 때문에 custionException을 만든다 (ex package - Exception401) public String ex401(Exception401 e) { // catch 자리 String html = """ <script> alert('${msg}'); location.href = "/login-form"; </script> """.replace("${msg}", e.getMessage()); // file을 return하는 것보다는 js로 하는게 낫다 return html; // RestControllerAdvice이므로 데이터를 리턴한다. } }
 

3. 실행 결과 (1) - Exception401

만약에 로그인을 하지 않고 url 주소창에 localhost:8080/board/save-form을 바로 입력하게 된다면 게시글은 로그인한 유저만이 작성 가능하기 때문에 아래의 사진과 같이 알림창에 내가 지정한 메세지가 뜨게 된다. 그리고 확인을 클릭하면 로그인 페이지로 이동하게 된다.
notion image
 

4. ExceptionApi401

AJAX 방식으로 호출하면 json으로 응답 처리해야 된다. 그래서 ExceptionApi401 클래스를 별도로 만들어 준다.
package shop.mtcoding.blog._core.error.ex; // Ajax는 이걸 터트려야 함! public class ExceptionApi401 extends RuntimeException { public ExceptionApi401(String message) { super(message); } }
 

5. GlobalExceptionHandler - exApi401()

Ajax를 적용한 경우에는 Resp<T> 클래스의 fail 메서드를 사용하여 실패시 status 코드와 메세지를 담고 json으로 serialize 되어 전달된다.
notion image
@ExceptionHandler(ExceptionApi401.class) public Resp<?> exApi401(ExceptionApi401 e) { // body에 null 담기고 json으로 serialize돼서 전달됨 return Resp.fail(401, e.getMessage()); }
 

6. 실행 결과 (2) - ExceptionApi401

만약에 로그인을 하지 않고 좋아요를 한다면 좋아요는 로그인한 유저만이 할 수 있는 기능이기 때문에 아래의 사진과 같이 알림창에 내가 지정한 메세지가 뜨게 된다. 이때 console.log로 responseBody를 확인해보면 body에는 null, msg에는 내가 설정한 메세지 내용과 status에는 내가 직접 지정한 상태코드가 제대로 들어가 있는 것을 확인할 수 있다.
notion image
notion image
 
실제 HTTP 상태코드는 200 ?
 
network를 확인해보면 로그인하지 않은 상태로 좋아요를 클릭했을 때 실제 HTTP 상태코드는 200임을 알 수 있다. 즉, 내가 시도한 기능인 좋아요는 로그인하지 않은 상태여서 실패하였지만 HTTP 요청 자체는 성공적으로 처리되었기 때문에 실제 HTTP 상태코드는 200이다. 실제 HTTP 상태코드에도 401을 던지고 싶다면 핸들러의 exApi04 메서드에서 response.setStatus(401)을 넘기면 되지만 프론트엔드에서 어차피 status를 parsing해서 써야되기 때문에 굳이 이런 방식을 사용하지 않는 것을 추천한다.
notion image
 
게시글 상세보기 페이지의 일부로서 좋아요는 내가 좋아요를 하고자 의도한 것이 아니라 실수로 클릭하게 되는 경우가 있는데 이 경우 로그인 화면으로 바로 이동하게 되면 UX 측면에서 잘 설계되었다고 보기 힘들다. 따라서 로그인 페이지로 이동하는 것보다는 그냥 원래 게시글 상세보기 화면 그대로 있는 것이 낫다.
notion image
 

3. 403 : 권한 없음

 

3. 실습 - postman

  1. ssar로 로그인
  1. 좋아요 id=2 삭제
 
 
 

4. 404 : 자원 없음

 

5. 400

 

6. 내가 처리하지 못한 Exception

 
GlobalExceptionHandler에 없는 경우
notion image
notion image
Share article

jay0628