try {
// 예외가 발생할 수 있는 상황 제시
String name = "김갑돌"
// 예외 발생 조건
if (name="김갑돌") {
throw new Exception("김갑돌은 안 받아줘요");
}
} // try 끝
catch(Exception e) {
예외 발생 시 구현할 내용
} finally {
예외 발생 여부, return 여부 등과 상관 없이 무조건 실행할 코드
}🌱
🔹 iii. 이 작업을 위해서는, 아래의 선행 작업이 필요하다
- ErrorResponse 클래스 생성
- Custom Exception 만들기
// RunTimEeXCEPTION 상속하여 클래스 만들기
public class CustomException extends RuntimeException{
// 이 때 HttpStatus을 따로 필드로 만들어 주는 까닭은,
// 나중에 예외처리할 때 이 값을 써주기 위해서임
private HttpStatus status;
// 생성자를 만들어 주면서 이 객체의 HttpStatus와 detailedMessage 생성
public CustomException(HttpStatus httpStatus, String message) {
super(message);
this.status = httpStatus;
}
}
➡️ CustomException 클래스 파일을 여러 개 만들면 유지보수가 힘듬
➡️ CustomException들을 하나의 ENUM 상수들로 관리해준다.
➡️ 근데 이대로 그냥 쓰기에는 Enum은 RunTimeException을 상속 불가하므로 RuntimeException.getDefaultMessage()등의 함수를 사용 못하므로,
➡️ 대분류별로 CustomException extends RuntimeException 클래스를 만들줘서, 그 클래스에서 또 ErrorCode ENUM을 객체로 받아준다.
// CustomException 클래스 파일을 여러 개 만들면 유지보수가 힘듬
// , 따라서 CustomException들을 하나의 ENUM 상수들로 관리해준다.
@RequiredArgsConstructor
@Getter
public enum ErrorCode {
FILE_UPLOAD_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드에 실패했습니다.");
private final HttpStatus status;
private final String message;
}
// ➡️ ENUM을 그냥 바로 쓰기에는, ENUM은 RunTimeException을 상속 불가하여 RuntimeException.getDefaultMessage()등의 함수를 사용 못하므로,
// ➡️ 대분류별로 CustomException extends RuntimeException 클래스를 만들줘서, 그 클래스에서 또 ErrorCode ENUM을 객체로 받아준다.
@Getter
public class PostException extends RuntimeException {
private final ErrorCode errorCode;
public PostException(ErrorCode errorCode) {
this.errorCode = errorCode;
}
public PostException(ErrorCode errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
}
@PostMapping("")
public void createMember(
@PathVariable Member member,
BindingResult bindingResult
) {
// 여기에서는 member 객체의 name 필드에 null이면 에러 생성
if (member.name == null) {
throw new CustomException(
}
public class ErrorResponse {
private LocalDateTime timestamp; // 에러가 발생한 시간
private int status; // 에러 상태코드
private String error; // 에러의 이름
private String message; // 에러의 구체적인 원인 메시지
private String path; // 에러가 발생한 경로
}
@Slf4j
public class GlobalExceptionHandler {
// 2. Exception이 발생하면 호출될 메서드에 @ExceptionHandler(Exception.class)를 붙여준다.
@ExceptionHandler(MemberException.class)
// - 이 때, 생성자 parameter로 아래 2개를 받아준다.
// i. Exception 발생 시 자동으로 생성되는 Exception e(e.getDefaultMessage, e.status.value()등 사용 목적)
// ii. 클라이언트 요청 시 자동 생성되는 HttpServletRequest를 인자로 받아준다.(request.getReqeusturl() 사용 목적)
public ResponseEntity<?> handleClientException(
MemberException e
, HttpServletRequest request
) {
// 로깅 처리
log.warn("exception occurred!! caused by: {}", e.getMessage());
// 4. 별도 클래스에서 아래 내용을 담은 클래스를 만들어 주고
// 5. 구체적인 에러 객체 생성해서 클라이언트에 전달해준다.
ErrorResponse error = ErrorResponse.builder()
.path(request.getRequestURI()) // path는 클라이언트가 호출 시 자돵 생성되는 HttlRequestServlet에서 가져옴
.message(e.getMessage())
.timestamp(LocalDateTime.now())
.status(e.getStatus().value()) // customException 만들 때, 생성자에 HttpStatus 타입인 status 필드 생성해 두었음
.error(e.getStatus().getReasonPhrase()) // HttpStatus객체.getReasonPhrase() : NOT FOUND, BAD REQUEST...
.build();
return ResponseEntity
.status(error.getStatus())
.body(error);
}
i. @Service, @Repository, @Controller에서 예외 처리 할 상황을 명시 해 준다.
ii. 예외 발생 시 구현할 내용을 @ControllerAdvice 클래스의 @ExceptionHandler(Exception.class) 메소드에 적어준다.
iii. 이 작업을 위해서는, 아래의 선행 작업이 필요하다
- ErrorResponse 클래스 생성(예외 발생 시, 클라이언트에 에러에 대한 구체적인 상황을 담아서 줄 것이므로)
- Custom Exception 만들기
@PostMapping("")
public void createMember(
// 여기에서 자동으로 MethodArgumentNotValidException 발생 됨
// 이 때, BindingResult도 parameter로 받아주면 exception 발생 안 됨 주의!
@PathVariable @Valid Member member,
) {
// 여기에는 정상 코드
}
public class ErrorResponse {
private LocalDateTime timestamp; // 에러가 발생한 시간
private int status; // 에러 상태코드
private String error; // 에러의 이름
private String message; // 에러의 구체적인 원인 메시지
private String path; // 에러가 발생한 경로
}
// 1. 클래스 이름에 '@ControllerAdvice'를 붙여 에러 발생하면 이 클래스로 향하게 한다.
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 2. Exception이 발생하면 호출될 메서드에 @ExceptionHandler(Exception.class)를 붙여준다.
@ExceptionHandler(MethodArgumentNotValidException.class)
// - 이 때, 생성자 parameter로 아래 2개를 받아준다.
// i. Exception 발생 시 자동으로 생성되는 Exception e(e.getDefaultMessage, e.status.value()등 사용 목적)
// ii. 클라이언트 요청 시 자동 생성되는 HttpServletRequest를 인자로 받아준다.(request.getReqeusturl() 사용 목적)
public ResponseEntity<?> handleBindingError(
MethodArgumentNotValidException e
, HttpServletRequest request
) {
// @valid를 쓸 경우 에러 발생 시 BindingResult가 자동생성 되고,
// BindingResult.getFielderrors.getDefaultMessage()에 @NotBlank(message="")의 메시지 전달됨
String message = e.getBindingResult().getFieldError().getDefaultMessage();
// 로깅 처리
log.warn("input value exception occurred!! caused by: {}", message);
// 구체적인 에러 객체 생성
ErrorResponse error = ErrorResponse.builder()
.path(request.getRequestURI()) // path는 클라이언트가 호출 시 자돵 생성되는 HttlRequestServlet에서 가져옴
.message(message) // @valid를 쓸 경우 에러 발생 시 BindingResult가 자동생성 되고,
// BindingResult.getFielderrors.getDefaultMessage()에 @NotBlank(message="")의 메시지 전달됨
.timestamp(LocalDateTime.now())
// MethodArgumentNotValidException의 내장 함수인 getStatusCode()는 HttpStatus.BAD_REQUEST를 retutrn함
.status(e.getStatusCode().value())
// HttpStatus 클래스의 내장함수인 toString()은 에러 메시지를 리턴함
.error(e.getStatusCode().toString())
.build();
return ResponseEntity
.status(error.getStatus())
.body(error);
}
}