[Spring] @(Rest)ControllerAdvice, @ExceptionHandler을 이용한 예외처리

·2024년 5월 17일

spring

목록 보기
6/18

1. Exception을 발생시키는 방법

1.1 잘못된 요청 전송

  • 존재하지 않는 URL접근 → 404 Error
  • 인증하지 않음 → 401 Error
  • 서버 내부 에러 → 500 Error
  • 이와 같이 잘못된 요청을 전송하여 에러가 발생될 수 있다.

1.2 throw를 사용해 직접 에러를 던져주기

throw new RuntimeException();
throw new IllegalArgumentException();

1.3 HttpServletResponse에 에러를 담아서 전송

response.sendError(403);
response.sendError(404, "404에러가 발생했습니다!");

2. 에러 페이지 적용

  • 스프링 부트에서는 예외가 발생하면 에러 페이지를 띄워준다.
    • 스프링 부트에서는 컨트롤러에서 에러가 발생하면 WAS - Filter - Servlet - Interceptor을 거쳐 Error Page Controller를 실행시킨다.

  • 위의 에러 페이지 대신 사용자 정의 에러 페이지를 적용하고 싶다면 resources/templates 또는 resources/static 경로에 "error" 라는 디렉토리 생성 후 404.html(Http 상태 코드.html)을 생성해주면 된다.
    • 4xx.html과 같이 400번대 에러를 모두 통일할 수 있다.

2.1 에러 페이지에 에러 정보 출력

  • applicaation.yml(혹은 application.properties)
server:
  error:
    include-exception: true
    include-message: always
    include-binding-errors: always
    include-stacktrace: always
  • 404.html
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>404 에러가 발생했습니다!</h1>
    <li>에러 정보</li>
    <ul>
        <li th:text="|timestamp: ${timestamp}|"></li>
        <li th:text="|path: ${path}|"></li>
        <li th:text="|status: ${status}|"></li>
        <li th:text="|message: ${message}|"></li>
        <li th:text="|error: ${error}|"></li>
        <li th:text="|exception: ${exception}|"></li>
        <li th:text="|errors: ${errors}|"></li>
        <li th:text="|trace: ${trace}|"></li>
    </ul>
</body>
</html>

3. Error Code 적용

3.1 ErrorCode (enum)

package com.sparta.nbcampspringpersonaltask.exception;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

/**
 * 예외처리 http 상태코드, 메시지 enum 클래스
 */
@Getter
@AllArgsConstructor
public enum ErrorCode {

    INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다."),
    SCHEDULE_NOT_FOUND(HttpStatus.NOT_FOUND, "선택한 일정은 존재하지 않습니다."),
    NOT_IMGFILE(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "이미지 파일만 업로드할 수 있습니다."),
    FILE_NOT_FOUND(HttpStatus.NOT_FOUND, "파일을 찾을 수 없습니다.");

    private final HttpStatus httpStatus;
    private final String message;
}

3.2 예외처리 클래스

  • 예외 처리를 위한 클래스
package com.sparta.nbcampspringpersonaltask.exception;

import lombok.Getter;

/**
 * 일정 예외
 */
@Getter
public class ScheduleException extends RuntimeException {

    private String result;
    private ErrorCode errorCode;
    private String message;

    public ScheduleException(ErrorCode errorCode) {
        this.result = "ERROR";
        this.errorCode = errorCode;
        this.message = errorCode.getMessage();
    }
}

4. @ExceptionHandler

  • 예외처리할 컨트롤러에 @ExceptionHandler 메소드 구현 (해당 컨트롤러 안에서만 적용됨)
/**
 * 일정 예외처리 핸들러
 * @param e ScheduleException
 * @return 예외 상태코드, 메시지
 */
@ExceptionHandler(ScheduleException.class)
public ResponseEntity<?> handleException(ScheduleException e){
    e.printStackTrace();
    return ResponseEntity.status(e.getErrorCode().getHttpStatus()).body(e.getErrorCode());
}

4. @RestControllerAdvice

  • 컨트롤러가 여러개이고, 모두 동일한 방식으로 처리하려 할 때 @ControllerAdvice, @RestControllerAdvice를 사용한다.
    • @ControllerAdivce는 @Controller에, @RestControllerAdivce는 @RestController에만 적용되는 것이 아닌 @RestController = @Controller + @ResponseBody와 같은 차이
  • annotations 속성을 설정해 특정 애노테이션에만 적용하거나, basePackages 속성을 설정해 특정 패키지만 적용할 수 있게 범위를 지정할 수 있다.
package com.sparta.nbcampspringpersonaltask.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 예외처리 핸들러 클래스
 */
//@RestControllerAdvice(annotations = Controller.class)
@RestControllerAdvice(basePackages = "com.sparta.nbcampspringpersonaltask.controller")
public class ExceptionManager {

    /**
     * 일정 예외처리 핸들러
     * @param e ScheduleException
     * @return 예외 상태코드, 메시지
     */
    @ExceptionHandler(ScheduleException.class)
    public ResponseEntity<?> handleException(ScheduleException e){
        e.printStackTrace();
        return ResponseEntity.status(e.getErrorCode().getHttpStatus()).body(e.getErrorCode());
    }

    /**
     * Valid 애노테이션을 이용한 유효성 검사 예외처리
     * @param e MethodArgumentNotValidException
     * @return 예외 상태코드, 메시지
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> handleException(MethodArgumentNotValidException e){
        e.printStackTrace();
        BindingResult bindingResult = e.getBindingResult();
        StringBuilder builder = new StringBuilder();
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            builder.append(fieldError.getField()).append(" : ").append(fieldError.getDefaultMessage()).append("\n");
        }
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ExceptionDto(builder.toString()));
    }
}

0개의 댓글