경희대학교 컴퓨터공학부 하계 리턴 백엔드(스프링부트) 스터디 6주차 - 트랙장 최현영
예외 처리
예외 클래스
https://velog.io/@codepark_kr/자바-이론-예외-처리
Checked / Unchecked Exception
Checked Exception
컴파일 단계에서 확인이 가능하고 예외 처리가 가능한 Exception
대개, Input/Output Stream[키보드 입력 및 모니터 출력]과 관련하여 알고리즘과 같은 입출력을 하려고 할때, 인텔리제이 또는 이클립스에서 IOException을 throws하라고 예외처리하라고 빨간줄 쳐진걸 본 경험이 있을텐데, IOException이 Checked Exception이기 때문이다.
Unchecked Exception
예외처리를 해보자
예외처리를 하는 방법은 대개, try{ ~ } catch(Exception e){ ~~}
구문을 사용한다.
스프링 부트에서 예외처리를 한다고 했을 때, 클라이언트의 요청이 예외를 발생시키면, 예외 처리된 내용이 다시 클라이언트의 응답으로 가야한다.
예외가 발생했을 때, 클라이언트에 오류 메시지를 전달하려면 각 레이어[서비스 레이어, 리파지토리 레이어 등]에서 발생한 예외를 엔드포인트[프레젠테이션(컨트롤러) 레이어]에 전달해야한다.
핸들러 클래스
5주차에서 우리는 Validation 실습을 진행할 때, 콘솔 로그에서 MethodArgumentNotValidException이 발생한 것을 살펴볼 수 있다.
MethodArgumentNotValidException 예외 핸들러 실습
그전에, 우선, 예외를 응답으로 반환하기 위해 ExceptionResponseDto를 생성하자.
package com.rebe.returnstudy.DTO;
import com.rebe.returnstudy.Exception.ErrCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.HashMap;
@Getter
@Setter
@NoArgsConstructor
public class ExceptionResponseDto {
private int status;
private Object message;
//RuntimeException을 위한 생성자
public ExceptionResponseDto(ErrCode errCode) {
this.message = errCode.getMessage();
this.status = errCode.getStatus();
}
//MethodArgumentNotValidException를 위한 생성자
public ExceptionResponseDto(int status, HashMap<String, String> message){
this.status = status;
this.message = message;
}
}
그리고, 어떤 커스텀 예외가 발생했는지 HttpStatusCode와 Message를 담아주기 위한 에러코드를 생성하기 위해 ErrCode 열거형을생성해 주자.
새 패키지 하나 만들어 주고, 열거식과 더불어, 각각의 상수 필드를 가지도록 Message와 status를 final로 선언해 주자.
package com.rebe.returnstudy.Exception;
import lombok.*;
import org.springframework.http.HttpStatus;
@Getter
@AllArgsConstructor
public enum ErrCode {
NOT_FOUND_MEMBER( "유저정보를 찾을 수 없습니다.", HttpStatus.NOT_FOUND.value()),
NOT_FOUND_POST( "게시글 정보를 찾을 수 없습니다.", HttpStatus.NOT_FOUND.value());
private final String message;
private final int status;
}
다음으로 @RestControllerAdvice와 @ExceptionHandler를 통해 전체 컨트롤러 클래스[엔드포인트]의 예외를 한곳에서 관리하도록 해주는 클래스를 작성하자
package com.rebe.returnstudy.Exception;
import com.rebe.returnstudy.DTO.ExceptionResponseDto;
import lombok.extern.slf4j.Slf4j;
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;
import java.util.HashMap;
@RestControllerAdvice
@Slf4j
public class RestControllerAdvisor {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseEntity<ExceptionResponseDto> handleMethodArgumentNotValidException(MethodArgumentNotValidException methodArgumentNotValidException){
BindingResult bindingResult = methodArgumentNotValidException.getBindingResult();
log.warn(methodArgumentNotValidException.getMessage());
HashMap<String, String> response = new HashMap<>();
for (FieldError fieldError : bindingResult.getFieldErrors()) {
String responseMessage = fieldError.getDefaultMessage() +
", 입력된 값: [" +
fieldError.getRejectedValue() +
"]";
response.put(fieldError.getField(), responseMessage);
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ExceptionResponseDto(HttpStatus.BAD_REQUEST.value(), response));
}
}
어떤 예외를 처리할지는 value 속성으로 지정해 줄 수 있다.
위에서 정의한 handleMethodArgumentNotValidException 메서드는 value=MethodArgumentNotValidException.class
로 지정해 주었으므로, 해당 예외가 발생했을 때, 호출되는 메서드인 것이다.
그리고 요청을 보내면, 어떤 필드가 유효성 검사에서 실패했는지 응답으로 반환된다.
CustomException 실습
package com.rebe.returnstudy.Exception;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@Getter
public class CustomException extends RuntimeException{
private final ErrCode errCode;
private final HttpStatus httpStatus;
public CustomException(final ErrCode errCode, final HttpStatus status) {
super(errCode.getMessage());
this.errCode = errCode;
this.httpStatus = status;
}
}
//RestControllerAdvisor
...
@ExceptionHandler(value = CustomException.class)
public ResponseEntity<ExceptionResponseDto> handleCustomeException(CustomException customException){
log.warn(customException.getMessage());
return ResponseEntity.status(customException.getHttpStatus())
.body(new ExceptionResponseDto(customException.getErrCode()));
}
비지니스 로직 실습
MemberService.class
Optional 객체 덕분에 존재하지 않는다면 throw 시켜 줌으로써, 예외처리 핸들러에게 처리하도록 위임하고, 정상적으로 Custom Exception Response가 실행된다.
b. PostSerivce.class
MemberService와 마찬가지이다.
정리
클라이언트[프론트엔드]와의 예외 통신은 정해진 규약으로 수행되어야한다. 서로간의 예외 Code를 정의하여 프론트엔드의 예외처리에 차질이 없도록 해주어야 한다.
예시)