Published 2023. 1. 9. 23:57
반응형
Exception을 발생시키는 방법
- 잘못된 요청 전송
- ex) 존재하지 않는 URL 접근 => 404 Error 발생
- 인증하지 않음 => 401 Error 발생
- 서버 내부 에러 => 500 Error 발생
- 이와 같이 직접 잘못된 요청을 전송하여 에러를 발생시킬 수 있음
- throw를 사용해 직접 에러를 던져주기
throw new RuntimeException(); throw new IllegalArgumentException();
- HttpServletResponse에 에러를 담아서 전송
response.sendError(403); response.sendError(404, "404에러가 발생했습니다!");
- 아래와 같이 Path Variable을 활용하여 원하는 에러를 발생 시킬 수도 있음
@GetMapping("/throw-exception/{errorCode}")
public void throwException(@PathVariable int errorCode, HttpServletResponse response) throws IOException {
response.sendError(errorCode, errorCode + "번 에러가 발생하였습니다!");
}
에러 페이지 적용 예제
- Spring Boot에서는 Exception이 발생하면 아래와 같이 Whitelabel Error Page가 출력됨
- Spring Boot에서는 Controller에서 에러가 발생하면 WAS(TOMCAT)로 에러가 전달되고 WAS에서는 다시 Filter, Servlet, Interceptor을 거쳐 Error Page Controller을 실행시키는 방식
- 이 때, 기본 설정이 아래와 같이 Whitelabel Error Page를 출력하게 되어있음
- 만약 이 에러 페이지 대신 내가 원하는 디자인의 에러 페이지를 적용하고 싶다면?
- 프로젝트의 resources/templates 또는 resources/static 경로에 "error" 라는 디렉토리 생성 후 404.html을 생성해 주면 됨
- 400 번대 에러를 모두 통일시키고 싶다면 4xx.html로 지정하면 되지만, 404.html과 같이 자세히 설정해 준 것이 우선순위가 더 높음
- (Thymeleaf 설치 후 resources/templates에 설치해야 밑에 나오는 예제 모두 진행 가능)
구현
- resources/templates/error/4xx.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>400 번대 에러가 발생했습니다!</h1>
</body>
</html>
- resources/templates/error/404.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>404 에러가 발생했습니다!</h1>
</body>
</html>
결과
- 401, 402, 403 에러 발생 시 출력 결과
- 404 에러 발생 시 출력 결과
에러 페이지에 에러 정보 출력 예제
- 에러 정보는 화면에 출력하는 것 보다는 로그로 남기는 것이 더 좋은 방법이라 하지만 화면에 에러 정보를 출력해보는 예제를 구현해 봄
구현
- applicaation.yml(혹은 application.properties)에 아래 코드 추가
# 화면에 에러 정보 출력
server:
error:
include-exception: true
include-message: always
include-binding-errors: always
include-stacktrace: always
- 위에서 만든 404.html 을 아래와 같이 수정 (Thymeleaf 사용)
<!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>
결과
- 404 에러 발생 시 출력되는 화면
Error Code 적용 예제
- 프로젝트를 개발하면서 발생할 수 있는 에러들이 존재
- 이 에러들을 미리 정리해 놓는다면 나중에 해당 에러들이 발생했을 때 쉽게 사용할 수 있음
ErrorCode (enum)
- 발생할 수 있을만한 에러들을 몇 개 정리해서 만들어 봄
- 로그인하거나 username으로 User을 찾는 등의 작업에서 username에 해당하는 User가 없는 경우 => USERNAME_NOT_FOUND
- 사용자가 인증, 인가에 실패한 경우 => INVALID_PERMISSION
- 회원가입 시 username이 중복되는 경우 => DUPLICATED_USER_NAME
- DB에서 에러가 나는 경우 => DATABASE_ERROR
- 위에서 정리한 에러들을 아래와 같이 정리해 봄
@Getter
@AllArgsConstructor
public enum ErrorCode {
USERNAME_NOT_FOUND(HttpStatus.NOT_FOUND, "유저를 찾을 수 없습니다"),
INVALID_PERMISSION(HttpStatus.UNAUTHORIZED, "권한이 없습니다"),
DUPLICATED_USER_NAME(HttpStatus.CONFLICT, "유저명이 중복됩니다"),
DATABASE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "DB에러가 발생하였습니다");
private HttpStatus status;
private String message;
}
ExceptionDto
- 위에서 정리한 에러들을 JSON 타입으로 출력하기 위한 DTO
- Method Overloading을 사용해 ErrorCode만 parameter로 넘어오면 기본 Error Message를 출력하고, ErrorCode와 message가 같이 넘어온다면 넘어온 Error Message를 출력하게 함
@Getter
public class ExceptionDto {
private String result;
private ErrorCode errorCode;
private String message;
public ExceptionDto(ErrorCode errorCode) {
this.result = "ERROR";
this.errorCode = errorCode;
this.message = errorCode.getMessage();
}
public ExceptionDto(ErrorCode errorCode, String message) {
this.result = "ERROR";
this.errorCode = errorCode;
this.message = message;
}
}
ExceptionController
- 에러가 발생한 경우를 가정하고 알맞는 ErrorCode로 설정
- 해당 ErrorCode로 Response의 status와 body를 만들어서 전송
@GetMapping("/throw-exception/1")
public ResponseEntity<?> throwException1() {
// ex) username으로 User을 찾을 수 없는 경우 발생한 경우
ErrorCode errorCode = ErrorCode.USERNAME_NOT_FOUND;
return ResponseEntity.status(errorCode.getStatus())
.body(new ExceptionDto(errorCode));
}
@GetMapping("/throw-exception/2")
public ResponseEntity<?> throwException2() {
// ex) 사용자가 인증, 인가에 실패한 경우
ErrorCode errorCode = ErrorCode.INVALID_PERMISSION;
return ResponseEntity.status(errorCode.getStatus())
.body(new ExceptionDto(errorCode, "ADMIN만 접근 가능합니다"));
}
@GetMapping("/throw-exception/3")
public ResponseEntity<?> throwException3() {
// ex) 회원가입 시 username이 중복되는 경우
ErrorCode errorCode = ErrorCode.DUPLICATED_USER_NAME;
return ResponseEntity.status(errorCode.getStatus())
.body(new ExceptionDto(errorCode));
}
@GetMapping("/throw-exception/4")
public ResponseEntity<?> throwException4() {
// ex) DB 에러가 발생한 경우
ErrorCode errorCode = ErrorCode.DATABASE_ERROR;
return ResponseEntity.status(errorCode.getStatus())
.body(new ExceptionDto(errorCode));
}
결과
반응형