프로그래밍에서 예외(exception)
란 입력 값이 처리가 불가능하거나 참조된 값이 잘못된 경우 등 애플리케이션이 정상적으로 동작못하는 상황
-> 개발자가 직접 처리할 수 있는 것이므로 미리 코드 설계를 통해 처리 가능
에러는 주로 자바의 가상머신에서 발생시키는 것으로 예외와 달리 애플리케이션 코드에서 처리할 수 있는 것이 거의 없음
-> 발생 시점에 처리하는 것이 아니라 미리 애플리케이션의 코드를 보며 원천적으로 차단해야 함
모든 예외클래스는 Throwable
클래스 상속 받음
CheckedException
은 컴파일 단계에서 확인 가능한 예외 상황
ex) IOException, SQLException
Unchecked Exception
은 런타임 단계에서 확인되는 예외상황
-> 문법상 문제는 없지만 프로그램 동작중에 발생하는 도중 예기치 않은 상황이 생겨 발생하는 예외 의미
ex) RuntimeException, NullPointerException, IllegalArgumentException, IndexOutofBoundException, SystemException
방법엔 크게 3가지
예외 복구 방법
은 예외 상황을 파악해서 문제를 해결하는 방식
-> 대표적으로 try/catch
int a = 1;
String b = "a";
try{
System.out.println(a + Integer.parseInt(b));
} catch(NumberFormatException e){
b = "2";
System.out.println(a + Integer.parseInt(b));
}
예외 처리 회피 방법
은 예외가 발생한 시점에 바로 처리하는 것이 아니라 예외가 발생한 메서드를 호출한 곳에서 에러 처리를 할 수 있게 전가하는 방식
-> throw 키워드를 사용해 어떤 예외가 발생했는지 호출부에 내용 전달
int a = 1;
String b = "a";
try{
System.out.println(a + Integer.parseInt(b));
} catch (NumberFormatException e){
throw new NumberFormatException("숫자가 아닙니다.");
}
예외 전환은 앞 두 방식을 섞은 방식
웹 서비스 애플리케이션에서는 외부에서 들어오는 요청에 담긴 데이터를 처리하는 경우 많음
-> 예외 발생시 이를 복구하여 정상 처리하기 보단 요청을 보낸 클라이언트에 어떤 문제가 발생했는지 상황을 전달하는 경우가 많음
예외가 발생했을 때 클라이언트에 오류 메세지를 전달하려면 각 레이어에서 발생한 예외를 엔드포인트 레벨인 컨트롤러로 전달해야 함
스프링 부트에서 예외를 처리하는 방식은 크게 두가지
@(Rest)ControllerAdvice
와 @ExceptionHandler
를 통해 모든 컨트롤러의 예외 처리RestControllerAdvice를 사용하면 결과값을 JSON 형태로 반환 가능
CustomExceptionHandler 클래스
@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 내 handleException 호출, {}, {}", 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);
}
}
@Restcontroller는 @Controller나 @RestController에서 발생하는 예외를 한 곳에서 편하게 관리할 수 있게 하는 기능 수행
예외를 관제하는 범위 지정 가능
@RestControllerAdvice(basePackages = "com.springboot.valid_exception")
@ExceptionHandler
는 @Controller나 @Restcontroller가 적용된 빈에서 발생하는 예외를 잡아 처리하는 메서드를 정의할 때 사용
어떤 예외 클래스를 처리할지는 value
속성으로 등록(배열 형식으로 여러 예외 클래스 등록 가능)
위 코드에서는 RunTimeException에 포함되는 각종 예외가 발생하는 경우 포착해서 처리
-> 메서드 내에는 클라이언트에게 오류가 발생했음을 알리는 응답 메세지 구성하여 리턴 하였음
예외를 발생시킬 컨트롤러 생성(ExceptionController)
@RestController
@RequestMapping("/exception")
public class ExceptionController {
@GetMapping
public void getRuntimeException(){
throw new RuntimeException("getRuntimeException 메서드 호출");
}
}
예외 발생 시켰을 때
핸들러 메서드가 위와 같은 응답을 출력
이 처럼 컨트롤러에서 던진 예외는 @RestControllerAdivce가 선언돼 있는 핸들러 클래스에서 매핑된 예외 타입을 찾아서 처리
-> @ControllerAdvice 및 @RestControllerAdvice는 전역 범위에서 예외 처리를 하기 때문에 특정 컨트롤러에서만 동작하는 @ExceptionHandler 메서드를 생성해 처리할 수도 있다
Exception컨트롤러에 메서드 추가
@RestController
@RequestMapping("/exception")
public class ExceptionController {
private final Logger LOGGER = LoggerFactory.getLogger(ExceptionController.class)
@GetMapping
public void getRuntimeException(){
throw new RuntimeException("getRuntimeException 메서드 호출");
}
@ExceptionHandler(value = RuntimeException.class)
public ResponseEntity<Map<String, String>> handleException(RuntimeException e,
HttpServletRequest request){
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
LOGGER.error("클래스 내 handleException 호출, {}, {}", 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 어노테이션을 사용한 메서드 선언 시 해당 클래스에 국한하여 예외 처리 가능
위 경우 예외를 다시 발생시킬시
com.springboot.valid_exception.controller.ExceptionController 클래스 내 handleException 호출, /exception, getRuntimeException 메서드 호출
@ControllerAdvice의 ExceptionHandler와 컨트롤러 내의 ExceptionHandler가 동일한 예외 타입을 처리한다면 좀더 우선순위가 높은 클래스의 핸들러 메서드가 사용됨
예를 들어
@ExceptionHandler(Exception.class) 와 @ExceptionHandler(NullPointerException.class)의 메서드가 있다면 좀더 구체적인 NullPointerException의 핸들러가 우선순위 가짐
그리고 ControllerAdvice()내 ExceptionHandler보단 컨트롤러 내 ExceptionHandler가 우선순위를 가지게 된다