관련 포스트: 220111 CustomException 리팩토링
말 그대로 사용자 정의 예외이다. Java 에서 정의해 놓은 수많은 예외가 있지만, 앱을 개발하다보면 당연하게도 자신의 로직과 완전히 맞는 예외를 찾기가 힘든 상황이 온다. 이 때, Java 의 Exception (혹은 RuntimeException) 을 상속받는 예외 클래스를 만들어서 이를 던지고 받고 물고 뜯고 하면 된다.
1. custom checked exception
예를 들어, new File() 에서 파일을 못찾는 경우엔 FileNotFoundException 이 발생된다.
try (Scanner file = new Scanner(new File(fileName))) {
if (file.hasNextLine()) return file.nextLine();
} catch(FileNotFoundException e) {
// Logging, etc
}
그런데 이게 진짜 없는건지, 이름이 잘못된건지 분류하고 싶을 수 있지 않은가?
이럴때 Exception 을 상속받는 IncorrectFileNameException 을 만들어서 파일이름이 잘못 된 경우를 따로 빼보자
// IncorrectFileNameExceptoin 정의
public class IncorrectFileNameException extends Exception {
public IncorrectFileNameException(String errorMessage, Throwable cause) {
super(errorMessage, cause);
}
}
// 활용
try (Scanner file = new Scanner(new File(fileName))) {
if (file.hasNextLine())
return file.nextLine();
} catch (FileNotFoundException e) {
if (!isCorrectFileName(fileName)) {
throw new IncorrectFileNameException("Incorrect filename : " + fileName, cause);
}
//...
}
2. custom unchecked exception
위 상황에서 파일에 읽을 내용이 없을 때 예외를 발생시켜야 한다고 가정하자. 이 예외는 실행하고 있을때만 발견할 수 있으므로 RuntimeException 을 상속받는 IncorrectFileExtentionException 을 만들어서 활용해보자.
// IncorrectFileExtentionException 정의
public class IncorrectFileExtensionException
extends RuntimeException {
public IncorrectFileExtensionException(String errorMessage, Throwable err) {
super(errorMessage, err);
}
}
// 활용
try (Scanner file = new Scanner(new File(fileName))) {
if (file.hasNextLine()) {
return file.nextLine();
} else {
throw new IllegalArgumentException("Non readable file");
}
} catch (FileNotFoundException err) {
if (!isCorrectFileName(fileName)) {
throw new IncorrectFileNameException(
"Incorrect filename : " + fileName , err);
}
//...
} catch(IllegalArgumentException err) {
if(!containsExtension(fileName)) {
throw new IncorrectFileExtensionException(
"Filename does not contain extension : " + fileName, err);
}
//...
}
예외 처리란 아주아주 어렵고 복잡하다. 이게 개념적으로만 복잡한 것이 아니라 코드도 복잡하게 만들어버리는 경우가 많다. Spring 에서는 이렇게 예외를 처리하는 로직을 한 곳에서 모아서 할 수 있게 만든 어노테이션이 있다.
바로 @ExceptionHandler 와 @ControllerAdvice 이다.
Controller 로 등록된 빈 내에서 발생하는 예외를 잡아서 하나의 메서드에서 처리해주는 기능을 함. 특정 컨트롤러에 대해서만 활성화 되는만큼, 범용적으로 사용할 수 없다. 한 컨트롤러에 할당되는 큰 단위의 catch 구문이라고 생각하면 된다.
public class MyClass {
...
@ExceptionHandelr({MyException01.class, MyException02.class})
public void handleException(Exception ex) {
// 로깅
// 에러 처리
}
}
- @ExceptionHandler 의 파라미터로 하나만 들어갈 수도 있고, 여러개 들어갈 수도 있다. (여러개 들어갈때는 중괄호 {} 넣어주기)
- 메서드의 반환자료형, 이름 등은 맘대로
- 또, 받은 예외를 파라미터로 받을 수 있다 (안받을 수도 있다)
Spring 공식 문서에서 다음과 같이 소개되어 있다.
Specialization of @Component for classes that declare @ExceptionHandler, @InitBinder, or @ModelAttribute methods to be shared across multiple @Controller classes.
그러니깐, ExceptionHandler 와 InitBinder, ModelAttribute 를 Controller 전역에 공유하게끔 한다 라는 말이다.
A convenience annotation that is itself annotated with @ControllerAdvice and @ResponseBody.
예외처리를 할 때, @ControllerAdvice 와 @ResponseBody 를 같이 써서 Restful 하게 응답을 때리는 상황이 많았나보다. 두개를 이어 붙여놓은 @RestControllerAdvice 를 만들어놨다.
다음과 같이 사용하면, 컨트롤러에서 발생된 모든 예외를 이 한 클래스에서 관리할 수 있게 된다.
@ControllerAdvice
public class 예외처리 {
@ExceptionHandler(Exception.class)
protected void handle() {
// 뭔가 함
}
}
@ExceptionHandler 가 모오오오든 컨트롤러에 등록된것과 같은 효과를 준다.
물론 이렇게 'Exception' 을 받아서 처리하면 호되게 혼날 수 있다.
ref
https://www.baeldung.com/java-new-custom-exception
https://jeong-pro.tistory.com/195
https://recordsoflife.tistory.com/30