기존 자바의 방식대로 예외를 처리하는 방법은 아래의 2가지 방법이 있다.
throws로 메소드 호출자에게 처리를 위임
try-catch문으로 최종처리
전체 코드에서 예외가 발생하는 부분마다 예외처리 코드가 작성되어있어 가독성을 해칠 뿐만 아니라 유지 관리 및 수정도 힘들어진다.
Advice가 처리하는 예외는 Spring에서 throws를 진행하다가 Dispatcher Servlet호출 이후(Spring Context)에서 처리된다. 그렇다면 Security와 같이 Dispatcher Servlet호출 전(Filter) 단계에서 예외가 발생하면 어떻게 해야할까?
클래스를 하나 만들고 @ControllerAdvice와 같은 어노테이션을 사용해서 @Controller에서 발생한 모든 예외를 해당 클래스로 모아 한꺼번에 처리한다.
이 때 하나의 처리 클래스만 사용하면 한 클래스에서 과도하게 많은 예외를 처리해야 할 수도 있기 때문에 기능이나 예외 종류별로 만들어서 모아 처리한다.
내가 작업하는 서비스나 코드의 상황에 맞게 구조와 처리를 커스텀 해야한다.
아래의 예시는 간단한 개념에 대한 예시로 실제로 사용하려면 더 디테일하게 사용해야 한다.
컨트롤러에서 발생한 모든종류의 예외를 동일하게 글로벌 처리하는 Handler
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e){
System.out.println("글로벌 처리");
e.printStackTrace();
return ResponseEntity.badRequest().body(e.getMessage());
}
}
0으로 나눴을 때 처리하는 메소드
@ExceptionHandler(ArithmeticException.class)
public ResponseEntity<String> handleArithmeticException(ArithmeticException e){
System.out.println("0으로 나눌 수 없습니다.");
e.printStackTrace();
return ResponseEntity.badRequest().body(e.getMessage());
}
규칙에 맞지 않게 들어온 데이터를 처리하는 메소드
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String,String>> handleMethodException(MethodArgumentNotValidException e){
System.out.println("입력값이 잘못 되었습니다");
Map<String,String> errors = new HashMap<>();
e.getBindingResult().getAllErrors().forEach((error)
-> {
String filedName = ((FieldError)error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(filedName, errorMessage);
});
return ResponseEntity.badRequest().body(errors);
}
validation이 위의 예외처리를 자동으로 생성한 것이기 때문에 직접 커스텀해서 만들 수도 있다. 동작은 아래와 같이 동일하다.