프로그램이 실행 중 어떤 원인에 의하여 오작동을 하거나 비정상적으로 종료되는 경우가 있는데, 이러한 결과를 초래하는 원인을 프로그램 오류라고 함
프로그램 오류는 발생시점에 따라 컴파일 에러, 런타임 에러 및 논리적 에러로 나눌 수 있음
컴파일 에러
런타임 에러
논리적 에러
이때 Exception 클래스를 Checked Exception(컴파일 예외)과 Unchecked Exception(런타임 예외)로 나눌 수도 있음
-> 오라클 문서에 따르면 클라이언트가 예외로부터 합리적으로 회복할 수 있는 경우 체크 예외를 사용해야 하며, 회복할 수 없는 경우 언체크 예외를 사용해야 함
예를 들어, 파일을 열기 전에 입력 파일 이름을 검증하고 이름이 유효하지 않으면 체크 예외가 발생하지만 이러한 경우에는 다른 파일 이름을 사용해 시스템 회복이 가능함. 하지만 입력 파일 이름이 널인 경우 언체크 예외를 발생시켜야 함
// Checked Exception
private static void checkedExceptionWithTryCatch() {
File file = new File("not_existing_file.txt");
try {
FileInputStream stream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
// Unchecked Exception
private static void divideByZero() {
int numerator = 1;
int denominator = 0;
int result = numerator / denominator; // 런타임에 ArithmeticException이 발생할 것입니다
}
프로그램 코드에 의해서 수습될 수 없는 심각한 오류
JVM 실행에 문제가 생긴 것으로, 에러가 발생하면 프로그램이 비정상적으로 종료됨
프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
개발자가 예외처리(try-catch문)를 해준다면 예외가 발생하더라도 프로그램의 비정상적인 종료를 막을 수도 있음
try {
// 예외가 발생할 가능성이 있는 문장들을 넣음
} catch (Exception1 e1) {
// Exception1이 발생했을 경우, 이를 처리하기 위한 문장을 적음
} catch (Exception2 e2) {
// Exception2가 발생했을 경우, 이를 처리하기 위한 문장을 적음
} finally {
// 예외의 발생여부에 상관없이 항상 수행되어야 하는 문장을 적음
}
throw 키워드를 사용해서 개발자가 고의로 예외를 발생시킬 수 있음
try {
Exception e = new Exception("고의로 발생시켰음");
throw e;
} catch (Exception e) {
System.out.println("에러 메시지 : " + e.getMessage());
e.printStackTrace();
}
// 실행 결과
// 에러 메시지 : 고의로 발생시켰음
// java.lang.Exception: 고의로 발생시켰음 at ExceptionEx6.main(ExceptionEx6.java:6)
예외가 발생하면, 예외를 복구해서 정상으로 처리하기 보다는 요청을 보낸 클라이언트에 어떤 문제가 발생했는지 상황을 전달하는 경우가 많음
예외가 발생했을 때 클라이언트에게 오류 메시지를 전달하려면, 각 레이어에서 발생한 예외를 엔드포인트 레벨인 컨트롤러로 전달해야 함
이렇게 전달받은 예외를 스프링부트에서 처리하는 방식으로 크게 두 가지가 있음
@RestControllerAdvice
public class CustomExceptionHandler {
private final Logger LOGGER = LoggerFactory.getLogger(CustomExceptionHandler.class);
@ExceptionHandler(value = RuntimeException.class)
public ResponseEntity<Map<String, String>> handleException(RuntimeException e,
HttpServletRequest request) {
HttpHeaders responseHeaders = new HttpHeaders();
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
LOGGER.error("Advice 내 exceptionHandler 호출, {}, {}", request.getRequestURI(),
e.getMessage());
Map<String, String> map = new HashMap<>();
map.put("error type", httpStatus.getReasonPhrase());
map.put("code", "400");
map.put("message", e.getMessage());
return new ResponseEntity<>(map, responseHeaders, httpStatus);
}
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleException(MethodArgumentNotValidException e,
HttpServletRequest request) {
// 클라이언트에게 오류가 발생했다는 걸 알라는 응답 메시지 구성
HttpHeaders responseHeaders = new HttpHeaders();
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
Map<String, String> map = new HashMap<>();
map.put("error type", httpStatus.getReasonPhrase());
map.put("code", "400");
map.put("message", e.getMessage());
return new ResponseEntity<>(map, responseHeaders, httpStatus);
}
@RestControllerAdvice
, @ControllerAdvice
는 애플리케이션 내 모든 @Controller
에서 발생하는 예외를 처리할 수 있게 함 @ExceptionHandler
을 메서드에 선언하고 특정 예외 클래스를 지정해주면 해당 예외가 발생했을 때 메서드에 정의한 로직으로 처리할 수 있음
도서 ‘Java의 정석’
https://velog.io/@jipark09/Java-Error와-Exception-차이
https://inpa.tistory.com/entry/JAVA-☕-에러Error-와-예외-클래스Exception-💯-총정리
https://velog.io/@daydream/Java-Checked-Exception과-Unchecked-Exception
도서 '스프링부트 핵심 가이드'
https://velog.io/@banjjoknim/RestControllerAdvice